123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- <template>
- <div
- class="history-view"
- @mousedown.passive="onMouseDown"
- @mousemove.passive="onMouseMove"
- @mouseup="onMouseUp"
- @mouseleave="onMouseLeave"
- >
- <video
- src="@/assets/videos/bg-history.mp4"
- class="bg"
- autoplay
- loop
- />
- <div
- class="gear-wrap"
- @click="doTest"
- >
- <img
- class="gear"
- :src="require(`@/assets/images/gear/表盘1_${gearFrameIdx.toString().padStart(5, '0')}.png`)"
- alt=""
- draggable="false"
- >
- </div>
- <div
- v-if="currentTimeIdx !== null"
- class="current-stage-name"
- >
- <h1>{{ stageList[currentTimeIdx].name }}</h1>
- <img
- class="underline"
- src="@/assets/images/underline-history-stage.png"
- alt=""
- draggable="false"
- >
- </div>
- <div
- class="time-axis-wrap"
- :style="{
- left: `${timeAxisLeft}px`,
- }"
- >
- <div
- v-for="index in timeAxisScaleRepeat"
- :key="index"
- class="scale-line"
- :style="{
- width: `${timeAxisScaleWidth}px`,
- marginRight: `${timeAxisScaleMargin}px`,
- opacity: computeTimeAxisScaleOpacity(index),
- }"
- />
- <div
- v-for="index in timeAxisScaleRepeat"
- :key="index"
- class="scale-line"
- :style="{
- width: `${timeAxisScaleWidth}px`,
- marginRight: `${timeAxisScaleMargin}px`,
- opacity: computeTimeAxisScaleOpacity(index + timeAxisScaleRepeat),
- }"
- />
- </div>
- <div
- v-for="stageLabel in stageLabelList"
- :key="stageLabel.name"
- class="stage-label"
- :style="{
- left: stageLabel.left + 'px',
- }"
- >
- {{ stageLabel.startTime }}
- <img
- draggable="false"
- src="@/assets/images/star.png"
- alt=""
- class="star animation-show-hide"
- >
- </div>
- <HistoryPersonCard
- v-for="(item) in personList"
- :key="item.id"
- class="history-person-card"
- :name="item.name"
- :img="item.img"
- :text="item.description || item.story"
- :tall-or-fat="item.tallOrFat"
- :style="{
- left: item.left + 'px',
- top: item.top + 'px',
- zIndex: item.zIndex,
- filter: item.filter,
- }"
- />
- </div>
- </template>
- <script>
- import { reactive, toRefs, ref, watch, onMounted, onBeforeUnmount, computed } from 'vue'
- import HistoryPersonCard from "@/components/HistoryPersonCard.vue"
- import axios from "axios"
- export default {
- components: {
- HistoryPersonCard,
- },
- setup () {
- const spaceEachPerson = 220
- // 时代阶段
- const stageList = reactive([
- {
- name: '开埠通商',
- startTime: '1843',
- endTime: '1894',
- startTimeFriendly: '19世纪40年代',
- endTimeFriendly: '19世纪90年代',
- components: '上海近代工业蹒跚起步',
- },
- {
- name: '曲折发展',
- startTime: '1895',
- endTime: '1936',
- startTimeFriendly: '19世纪90年代',
- endTimeFriendly: '20世纪30年代',
- components: '上海近代工业起起伏伏',
- },
- {
- name: '步履维艰',
- startTime: '1937',
- endTime: '1949',
- startTimeFriendly: '20世纪30年代',
- endTimeFriendly: '20世纪50年代',
- components: '上海民族工业几经崩溃',
- },
- {
- name: '筚路蓝缕',
- startTime: '1950',
- endTime: '1978',
- startTimeFriendly: '20世纪50年代',
- endTimeFriendly: '20世纪70年代',
- components: '上海现代工业扬帆起航',
- },
- {
- name: '改革开放',
- startTime: '1979',
- endTime: '1996',
- startTimeFriendly: '20世纪70年代',
- endTimeFriendly: '20世纪90年代',
- components: '上海工业凤凰涅槃',
- },
- {
- name: '战略负重',
- startTime: '1997',
- endTime: '2011',
- startTimeFriendly: '20世纪90年代',
- endTimeFriendly: '本世纪10年代',
- components: '上海工业筑梦前行',
- },
- {
- name: '创新驱动',
- startTime: '2012',
- endTime: '2020',
- startTimeFriendly: '本世纪10年代',
- endTimeFriendly: '本世纪20年代',
- components: '上海工业转型升级',
- },
- {
- name: '追梦未来',
- startTime: '2021',
- endTime: '2035',
- startTimeFriendly: '本世纪20年代',
- endTimeFriendly: '本世纪30年代',
- components: '上海工业再创辉煌',
- },
- ])
- // 获取数据
- Promise.allSettled(stageList.map((stageItem) => {
- return axios({
- method: 'post',
- url: `https://sit-shgybwg.4dage.com/api/show/history/pageList`,
- headers: {
- "Content-Type": "application/json",
- },
- data: {
- stage: stageItem.name
- },
- }).then((res) => {
- return res.data.data.records
- }).then((res) => {
- stageItem.personList = res
- const personWidth = spaceEachPerson
- stageItem.width = personWidth * res.length
- }).then(() => {
- // 获取各人物的照片
- for (const personItem of stageItem.personList) {
- api.getHistoryDetail(personItem.id).then((res) => {
- try {
- personItem.img = process.env.VUE_APP_API_ORIGIN + res.file[0].filePath
- } catch (e) {
- console.log(e)
- }
- })
- }
- })
- })).then((res) => {
- // 计算各个时代的起始终止位置
- let temp = 0
- for (const stageItem of stageList) {
- stageItem.startPos = temp
- stageItem.endPos = temp + stageItem.width
- temp = stageItem.endPos
- }
- maxTranslateLength.value = stageList[stageList.length - 1].endPos
- })
- // 用户操作相关
- const isMouseDown = ref(false)
- const lastMoveEventTimeStamp = ref(0)
- const lastCursorPos = ref(0)
- // 镜头平移相关
- const moveSpeed = ref(0)
- const translateLength = ref(0)
- const maxTranslateLength = ref(0)
- // 用户操作改变镜头平移速度
- function onMouseDown(e) {
- isMouseDown.value = true
- moveSpeed.value = 0
- lastMoveEventTimeStamp.value = 0
- lastAnimationTimeStamp = Date.now()
- lastCursorPos.value = e.clientX
- if (beginAutoMoveIntervalId) {
- clearInterval(beginAutoMoveIntervalId)
- beginAutoMoveIntervalId = null
- }
- if (isAutoMoving.value) {
- isAutoMoving.value = false
- }
- }
- function onMouseUp(e) {
- isMouseDown.value = false
- beginAutoMoveIntervalId = setInterval(() => {
- if (moveSpeed.value === 0) {
- isAutoMoving.value = true
- }
- }, 2000)
- }
- function onMouseLeave() {
- isMouseDown.value = false
- }
- function onMouseMove(e) {
- if (isMouseDown.value) {
- // 疯狂操作的极端情况下两个时间戳之间的时差会不合理,甚至为0
- if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
- // 更新speed
- const currentMoveSpeed = - (e.clientX - lastCursorPos.value) / (e.timeStamp - lastMoveEventTimeStamp.value) * 1
- moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
- lastCursorPos.value = e.clientX
- }
- lastMoveEventTimeStamp.value = e.timeStamp
- }
- }
- /**
- * 镜头自动平移
- */
- let isAutoMoving = ref(false)
- let beginAutoMoveIntervalId = null
- watch(isAutoMoving, (vNew) => {
- if (vNew) {
- moveSpeed.value = 0.03
- } else {
- moveSpeed.value = 0
- }
- })
- onBeforeUnmount(() => {
- clearInterval(beginAutoMoveIntervalId)
- })
- /**
- * 动画帧相关
- */
- let lastAnimationTimeStamp = 0
- let animationFrameId = null
- // 在每一帧更新镜头速度、位置
- function animationFrameTask() {
- const timeStamp = Date.now()
- const timeElapsed = timeStamp - lastAnimationTimeStamp
- if (!isAutoMoving.value) {
- // 速度减慢
- if (moveSpeed.value > 0) {
- moveSpeed.value -= ($.valueisMobile ? 0.001 : 0.003) * timeElapsed
- if (moveSpeed.value < 0) {
- moveSpeed.value = 0
- }
- } else if (moveSpeed.value < 0) {
- moveSpeed.value += ($.valueisMobile ? 0.001 : 0.003) * timeElapsed
- if (moveSpeed.value > 0) {
- moveSpeed.value = 0
- }
- }
- }
- // 根据速度更新位置
- translateLength.value += moveSpeed.value * timeElapsed
- if (translateLength.value < 0) {
- translateLength.value = 0
- moveSpeed.value = 0
- } else if (translateLength.value > maxTranslateLength.value) {
- if (isAutoMoving.value) {
- translateLength.value = 0
- } else {
- translateLength.value = maxTranslateLength.value
- moveSpeed.value = 0
- }
- }
- lastAnimationTimeStamp = timeStamp
- animationFrameId = requestAnimationFrame(animationFrameTask)
- }
- onMounted(() => {
- animationFrameId = requestAnimationFrame(animationFrameTask)
- })
- onBeforeUnmount(() => {
- cancelAnimationFrame(animationFrameId)
- })
- // 动画
- // 恢复历史位置
- // if (this.longImageTranslateLengthRecord) {
- // this.translateLength = this.longImageTranslateLengthRecord
- // this.setLongImageTranslateLengthRecord(null)
- // }
- /**
- * 当前时代相关
- */
- const currentTimeIdx = ref(0)
- watch(translateLength, (vNew) => {
- for (let index = 0; index < stageList.length; index++) {
- const stageItem = stageList[index]
- if (vNew >= (stageItem.startPos - window.innerWidth / 7) && vNew <= (stageItem.endPos - window.innerWidth / 7)) {
- currentTimeIdx.value = index
- break
- } else {
- currentTimeIdx.value = null
- }
- }
- })
- // 时间轴
- const timeAxisLeft = ref(0)
- const timeAxisScaleWidth = 2
- const timeAxisScaleMargin = 120
- const timeAxisScaleRepeat = 20
- watch(translateLength, (vNew) => {
- timeAxisLeft.value = -(vNew % ((timeAxisScaleWidth + timeAxisScaleMargin) * timeAxisScaleRepeat))
- })
- const windowCenterX = window.innerWidth / 2
- function computeTimeAxisScaleOpacity(index) { // 注意index是从1开始的
- const scaleCenterXInWindow = (timeAxisScaleWidth + timeAxisScaleMargin) * (index - 1) + timeAxisScaleWidth / 2 + timeAxisLeft.value
- return String(Math.max(1 - Math.abs(windowCenterX - scaleCenterXInWindow) / (windowCenterX), 0))
- }
- // 齿轮
- const gearFrameNumberTotal = 24
- const gearFrameIdx = ref(0)
- const gearFrameNumberPerScale = 3
- const translateLengthPerGrearFrame = (timeAxisScaleWidth + timeAxisScaleMargin) / gearFrameNumberPerScale
- watch(translateLength, (vNew) => {
- const framePassed = Math.round(translateLength.value / translateLengthPerGrearFrame)
- if (framePassed >= 0) {
- gearFrameIdx.value = framePassed % gearFrameNumberTotal
- } else {
- gearFrameIdx.value = gearFrameNumberTotal + framePassed % gearFrameNumberTotal - 1
- }
- })
- // 各个时代标签
- const stageLabelList = computed(() => {
- return stageList.map((item) => {
- if (item?.personList?.length) {
- return {
- name: item.name,
- initialLeft: (item.startPos + 10) * 3,
- left: (item.startPos + 10) * 3,
- startTime: item.startTime + '年',
- }
- } else {
- return undefined
- }
- }).filter((item) => {
- return !!item
- })
- })
- watch(translateLength, (vNew) => {
- for (const iterator of stageLabelList.value) {
- iterator.left = iterator.initialLeft - vNew * 3
- }
- })
- // 各个人物
- const personList = computed(() => {
- const ret = []
- stageList.map((stageItem) => {
- if (stageItem.personList) {
- for (const personItem of stageItem.personList) {
- const tallOrFat = (ret.length % 2 === 0 ? 'tall' : 'fat')
- ret.push({
- ...personItem,
- initialLeft: (ret.length * spaceEachPerson + spaceEachPerson / (tallOrFat === 'tall' ? 2 : 1.3)) * (tallOrFat === 'tall' ? 2 : 1.5),
- left: (ret.length * spaceEachPerson + spaceEachPerson / (tallOrFat === 'tall' ? 2 : 1.3)) * (tallOrFat === 'tall' ? 2 : 1.5),
- top: Math.random() * 70 + (tallOrFat === 'tall' ? 250 : 350),
- tallOrFat,
- zIndex: tallOrFat === 'tall' ? 5 : 4,
- filter: `brightness(${tallOrFat === 'tall' ? '1' : '0.75'})`,
- })
- }
- }
- })
- return ret
- })
- watch(translateLength, (vNew) => {
- for (const iterator of personList.value) {
- iterator.left = iterator.initialLeft - vNew * (iterator.tallOrFat === 'tall' ? 2 : 1.5)
- }
- })
- return {
- onMouseDown,
- onMouseMove,
- onMouseUp,
- onMouseLeave,
- timeAxisLeft,
- timeAxisScaleWidth,
- timeAxisScaleMargin,
- timeAxisScaleRepeat,
- computeTimeAxisScaleOpacity,
- gearFrameIdx,
- stageList,
- currentTimeIdx,
- stageLabelList,
- personList,
- }
- }
- }
- </script>
- <style lang="less" scoped>
- .history-view {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- user-select: none;
- >video.bg{
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- >.gear-wrap {
- position: absolute;
- top: 0;
- left: 50%;
- transform: translateX(-50%);
- width: calc(100vw * 940 / 1920);
- >img.gear{
- width: 100%;
- }
- }
- >.current-stage-name {
- position: absolute;
- left: 50%;
- top: 10.5vw;
- transform: translateX(-50%);
- width: 250px;
- text-align: center;
- >h1{
- font-size: 36px;
- font-family: Source Han Sans CN-Bold, Source Han Sans CN;
- font-weight: bold;
- color: #FFFFFF;
- line-height: 42px;
- }
- >img.underline{
- margin-top: -20px;
- width: 100%;
- }
- }
- >.time-axis-wrap{
- bottom: 32.4%;
- position: absolute;
- display: flex;
- >.scale-line{
- height: 30px;
- background-color: #91886b;
- }
- }
- >.stage-label{
- position: absolute;
- bottom: 15.7%;
- font-size: 96px;
- font-family: Source Han Sans CN-Bold, Source Han Sans CN;
- font-weight: bold;
- color: #FFFFFF;
- line-height: 97px;
- text-shadow: 10px 0px 0 black;
- z-index: 10;
- width: 407px;
- text-align: left;
- >img.star{
- position: absolute;
- left: 0;
- top: 0;
- transform: translate(-50%, -80%);
- }
- }
- >.history-person-card{
- position: absolute;
- }
- }
- </style>
|