Kaynağa Gözat

文物展示相关代码提取为一个组件

任一存 3 yıl önce
ebeveyn
işleme
ec9b87529d
3 değiştirilmiş dosya ile 309 ekleme ve 229 silme
  1. 278 0
      src/components/TreasureDisplay.vue
  2. 2 0
      src/main.js
  3. 29 229
      src/views/HomeView.vue

+ 278 - 0
src/components/TreasureDisplay.vue

@@ -0,0 +1,278 @@
+<template>
+  <div
+    class="treasure-display"
+  >
+    <img
+      v-if="['idle', 'fade-in'].includes(displayState)"
+      ref="treasure"
+      class="treasure"
+      :style="{
+        height: treasureInitialHeightPercent + '%',
+        opacity: treasureOpacity,
+      }"
+      src="@/assets/treasure.png"
+      alt=""
+      @dragstart.prevent
+    >
+    <button
+      v-if="displayState !== 'idle'"
+      class="btn-stop-treasure-display"
+      @click="stopDisplay"
+    >
+      <img
+        src="@/assets/button-stop.png"
+        alt=""
+      >
+    </button>
+    <SerialFrames
+      v-show="displayState === 'display'"
+      ref="treasure-serial-frames"
+      class="treasure-serial-frames"
+      :frame-total-num="treasureFrameTotalNum"
+      :frame-interval="40"
+      :image-src-func="(index) => require(`@/assets/treasure-frames/3_${(index - 1).toString().padStart(5, '0')}.jpg`)"
+      @over="onSerialFramesOver"
+    />
+    <div
+      v-if="displayState !== 'idle'"
+      class="fade-mask"
+      :style="{
+        opacity: maskOpacity,
+      }"
+    >
+      <img
+        v-show="displayState === 'fade-out'"
+        src="@/assets/treasure-last-frame.jpg"
+        alt=""
+      >
+    </div>
+  </div>
+</template>
+
+<script>
+import TWEEN from '@tweenjs/tween.js'
+
+export default {
+  props: {
+    treasureFadeInDuration: {
+      type: Number,
+      default: 3000,
+    },
+    treasureFadeOutDuration: {
+      type: Number,
+      default: 2000,
+    },
+    treasureFrameTotalNum: {
+      type: Number,
+      required: true,
+    },
+    treasureInitialHeightPercent: {
+      type: Number,
+      required: true, // 0~100
+    },
+    treasureFrameWidth: {
+      type: Number,
+      required: true, // 单位:px
+    },
+    treasureFrameHeight: {
+      type: Number,
+      required: true, // 单位:px
+    },
+  },
+  emits: ['display-started', 'display-stopped'],
+  data() {
+    return {
+      // 动画帧相关
+      animationFrameId: null,
+
+      displayState: 'idle', // idle, fade-in, display, fade-out
+
+      // 文物淡入相关
+      treasureFadeInProgress: {
+        value: 0, // 0~100
+      },
+      treasureFadeInInitialLeft: 0,
+      treasureFadeInInitialTop: 0,
+      treasureFadeInFinalLeft: 0,
+      treasureFadeInFinalTop: 0,
+      treasureFinalHeightPercent: 0, // 0~100
+      treasureFadeInTween: null,
+
+      // 文物淡出相关
+      treasureFadeOutProgress: {
+        value: 0, // 0~100
+      },
+      treasureFadeOutTween: null,
+    }
+  },
+  computed: {
+    treasureOpacity() {
+      if (this.displayState === 'fade-in') {
+        if (this.treasureFadeInProgress.value > 50) {
+          return 1
+        } else {
+          return this.treasureFadeInProgress.value / 50
+        }
+      } else if (this.displayState === 'fade-out') {
+        if (this.treasureFadeOutProgress.value < 50) {
+          return 1
+        } else {
+          return 1 - (this.treasureFadeOutProgress.value - 50) / 50
+        }
+      } else {
+        return 0
+      }
+    },
+    maskOpacity() {
+      if (this.displayState === 'fade-in') {
+        if (this.treasureFadeInProgress.value > 50) {
+          return 1
+        } else {
+          return this.treasureFadeInProgress.value / 50
+        }
+      } else if (this.displayState === 'display') {
+        return 1
+      } else if (this.displayState === 'fade-out') {
+        return (100 - this.treasureFadeOutProgress.value) / 100
+      } else {
+        return 0
+      }
+    },
+  },
+  watch: {
+    'treasureFadeInProgress.value': {
+      handler(vNew, vOld) {
+        if (this.treasureFadeInProgress.value > 50) {
+          this.$refs.treasure.style.left = this.treasureFadeInInitialLeft + (this.treasureFadeInProgress.value - 50) / 50 * (this.treasureFadeInFinalLeft - this.treasureFadeInInitialLeft) + 'px'
+          this.$refs.treasure.style.top = this.treasureFadeInInitialTop + (this.treasureFadeInProgress.value - 50) / 50 * (this.treasureFadeInFinalTop - this.treasureFadeInInitialTop) + 'px'
+          this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(${1 + (this.treasureFadeInProgress.value - 50) / 50 * (this.treasureFinalHeightPercent / this.treasureInitialHeightPercent - 1)})`
+        } else {
+          this.$refs.treasure.style.left = this.treasureFadeInInitialLeft + 'px'
+          this.$refs.treasure.style.top = this.treasureFadeInInitialTop + 'px'
+          this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(1)`
+        }
+      }
+    },
+  },
+  mounted() {
+    const that = this
+    this.animationFrameId = requestAnimationFrame(this.animationFrameTask)
+
+    this.treasureFadeInTween = new TWEEN.Tween(that.treasureFadeInProgress)
+    this.treasureFadeInTween.to({
+      value: 100,
+    }, this.treasureFadeInDuration)
+    this.treasureFadeInTween.onComplete(() => {
+      this.displayState = 'display'
+      this.$refs['treasure-serial-frames'].play()
+    })
+    this.treasureFadeInTween.easing(TWEEN.Easing.Linear.None)
+
+    this.treasureFadeOutTween = new TWEEN.Tween(that.treasureFadeOutProgress)
+    this.treasureFadeOutTween.to({
+      value: 100,
+    }, this.treasureFadeOutDuration)
+    this.treasureFadeOutTween.onComplete(() => {
+      this.displayState = 'idle'
+      this.$emit('display-stopped')
+    })
+    this.treasureFadeOutTween.easing(TWEEN.Easing.Linear.None)
+  },
+  unmounted() {
+    cancelAnimationFrame(this.animationFrameId)
+    this.treasureFadeInTween.stop()
+    this.treasureFadeOutTween.stop()
+    clearInterval(this.treasureFrameIntervalId)
+  },
+  methods: {
+    animationFrameTask() {
+      TWEEN.update()
+      this.animationFrameId = requestAnimationFrame(this.animationFrameTask)
+    },
+
+    startDisplay() {
+      if (this.displayState !== 'idle') {
+        return
+      }
+
+      // 计算淡入过渡的起始终止位置
+      this.treasureFadeInInitialLeft = this.$refs.treasure.offsetLeft
+      this.treasureFadeInInitialTop = this.$refs.treasure.offsetTop
+      this.treasureFadeInFinalLeft = window.innerWidth / 2 - this.$refs.treasure.offsetParent.offsetLeft
+      this.treasureFadeInFinalTop = window.innerHeight / 2 - this.$refs.treasure.offsetParent.offsetTop
+
+      // 计算淡入过渡终止时文物需要放大的倍数
+      const viewportRatio = window.innerWidth / window.innerHeight
+      const frameRatio = this.treasureFrameWidth / this.treasureFrameHeight
+      if (viewportRatio >= frameRatio) { // 视口比序列帧扁,序列帧高度会缩放到和视口相同,所以序列帧的第一帧作为淡入时的文物图片,也缩放到和视口等高即可。
+        this.treasureFinalHeightPercent = 100
+      } else { // 视口比序列帧窄,序列帧宽度会缩放到和视口相同,所以序列帧的第一帧作为淡入时的文物图片,也缩放到和视口等宽即可。
+        const treasureFinalHeight = this.treasureFrameHeight / this.treasureFrameWidth * window.innerWidth
+        this.treasureFinalHeightPercent = treasureFinalHeight / window.innerHeight * 100
+      }
+
+      this.displayState = 'fade-in'
+      this.treasureFadeInTween.start()
+      this.$emit('display-started')
+    },
+    stopDisplay() {
+      this.displayState = 'idle'
+      this.treasureFadeInTween.stop()
+      this.treasureFadeOutTween.stop()
+      this.$refs['treasure-serial-frames'].stop()
+      if (this.$refs.treasure) {
+        this.$refs.treasure.style.left = this.treasureFadeInInitialLeft + 'px'
+        this.$refs.treasure.style.top = this.treasureFadeInInitialTop + 'px'
+        this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(1)`
+      }
+      this.$emit('display-stopped')
+    },
+    onSerialFramesOver() {
+      this.displayState = 'fade-out'
+      this.treasureFadeOutTween.start()
+    },
+  },
+
+}
+</script>
+
+<style lang="less" scoped>
+.treasure-display {
+  .treasure {
+    position: absolute;
+    left: 49%;
+    top: 32.5%;
+    z-index: 2;
+    transform: translate(-50%, -50%);
+  }
+  .fade-mask {
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background-color: #bfbfa7;
+    z-index: 1;
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+  }
+  .btn-stop-treasure-display {
+    position: fixed;
+    top: 20px;
+    right: 20px;
+    z-index: 4;
+    border: none;
+  }
+  .treasure-serial-frames {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 3;
+  }
+}
+</style>

+ 2 - 0
src/main.js

@@ -4,10 +4,12 @@ import router from './router'
 import store from './store'
 import HotSpot from '@/components/HotSpot.vue'
 import SerialFrames from '@/components/SerialFrames.vue'
+import TreasureDisplay from '@/components/TreasureDisplay.vue'
 
 createApp(App)
   .use(store)
   .use(router)
   .component('HotSpot', HotSpot)
   .component('SerialFrames', SerialFrames)
+  .component('TreasureDisplay', TreasureDisplay)
   .mount('#app')

+ 29 - 229
src/views/HomeView.vue

@@ -77,7 +77,7 @@
         @dragstart.prevent
       >
       <HotSpot
-        v-if="[3].includes(tourState)"
+        v-if="canMoveCamera"
         class="hot-spot"
         @click="onClickPeopleFarHotSpot"
       />
@@ -96,22 +96,20 @@
         @dragstart.prevent
       >
       <HotSpot
-        v-show="[3].includes(tourState)"
+        v-show="canMoveCamera"
         class="hot-spot"
         @click="onClickPeopleNearHotSpot"
       />
-      <img
-        v-if="[0, 3].includes(tourState)"
-        ref="treasure"
-        class="treasure"
-        :style="{
-          height: treasureInitialHeightPercent + '%',
-          opacity: treasureOpacity,
-        }"
-        src="@/assets/treasure.png"
-        alt=""
-        @dragstart.prevent
-      >
+      <TreasureDisplay
+        ref="treasure-display"
+        class="treasure-display"
+        :treasure-frame-total-num="treasureFrameTotalNum"
+        :treasure-initial-height-percent="treasureInitialHeightPercent"
+        :treasure-frame-width="treasureFrameWidth"
+        :treasure-frame-height="treasureFrameHeight"
+        @display-started="canMoveCamera = false"
+        @display-stopped="canMoveCamera = true"
+      />
     </div>
     <img
       class="introduce"
@@ -122,47 +120,10 @@
       alt=""
       @dragstart.prevent
     >
-
-    <div
-      v-if="[0, 1, 2].includes(tourState)"
-      class="fade-mask"
-      :style="{
-        opacity: maskOpacity,
-      }"
-    >
-      <img
-        v-show="[2].includes(tourState)"
-        src="@/assets/treasure-last-frame.jpg"
-        alt=""
-      >
-    </div>
-
-    <button
-      v-if="[0, 1, 2].includes(tourState)"
-      class="btn-stop-treasure-display"
-      @click="onClickStopTreasureDisplay"
-    >
-      <img
-        src="@/assets/button-stop.png"
-        alt=""
-      >
-    </button>
-
-    <SerialFrames
-      v-show="[1].includes(tourState)"
-      ref="treasure-serial-frames"
-      class="treasure-serial-frames"
-      :frame-total-num="treasureFrameTotalNum"
-      :frame-interval="40"
-      :image-src-func="(index) => require(`@/assets/treasure-frames/3_${(index - 1).toString().padStart(5, '0')}.jpg`)"
-      @over="tourState=2, treasureFadeOutTween.start()"
-    />
   </div>
 </template>
 
 <script>
-import TWEEN from '@tweenjs/tween.js'
-
 const initialLandscapePositionLeft = '18.491%'
 const initialCloud2PositionLeft = '35%'
 const initialCloud1PositionLeft = '0%'
@@ -181,19 +142,23 @@ const introduceSpeedRate = 0.6
 
 const translateLengthRightBorder = 9000 // 单位:px
 
-const treasureFadeInDuration = 3000 // 单位:ms
-const treasureFadeOutDuration = 2000 // 单位:ms
-
-const treasureFrameTotalNum = 196
-
-const treasureInitialHeightPercent = 8 // 0~100
-const treasureFrameWidth = 1366 // 单位:px
-const treasureFrameHeight = 768 // 单位:px
+// 文物展示相关
+const treasureDisplayConfigMixins = {
+  data() {
+    return {
+      treasureFrameTotalNum: 196,
+      treasureInitialHeightPercent: 8,
+      treasureFrameWidth: 1366,
+      treasureFrameHeight: 768,
+    }
+  },
+}
 
 export default {
   name: 'HomeView',
   components: {
   },
+  mixins: [treasureDisplayConfigMixins],
   data() {
     return {
       // 鼠标拖拽相关
@@ -205,7 +170,7 @@ export default {
       lastAnimationTimeStamp: 0,
       animationFrameId: null,
 
-      tourState: 3, // 0:文物淡入过渡阶段;1:文物三维展示阶段;2:文物渐出过渡阶段;3:镜头平移阶段
+      canMoveCamera: true,
 
       // 镜头平移相关
       translateLength: 0,
@@ -218,80 +183,12 @@ export default {
       peopleNearPositionLeft: initialPeopleNearPositionLeft,
       introducePositionLeft: initialIntroducePositionLeft,
 
-      // 文物淡入相关
-      treasureFadeInProgress: {
-        value: 0, // 0~100
-      },
-      treasureFadeInInitialLeft: 0,
-      treasureFadeInInitialTop: 0,
-      treasureFadeInFinalLeft: 0,
-      treasureFadeInFinalTop: 0,
-      treasureInitialHeightPercent,
-      treasureFinalHeightPercent: 0, // 0~100
-      treasureFadeInTween: null,
-
-      // 文物展示相关
-      treasureFrameTotalNum,
-
-      // 文物淡出相关
-      treasureFadeOutProgress: {
-        value: 0, // 0~100
-      },
-      treasureFadeOutTween: null,
-
       // 远处人物变色相关
       peopleFarColorStatus: 'no-color', // 'no-color', 'color'
       isPeopleFarColorChanging: false,
     }
   },
-  computed: {
-    treasureOpacity() {
-      if (this.tourState === 0) {
-        if (this.treasureFadeInProgress.value > 50) {
-          return 1
-        } else {
-          return this.treasureFadeInProgress.value / 50
-        }
-      } else if (this.tourState === 2) {
-        if (this.treasureFadeOutProgress.value < 50) {
-          return 1
-        } else {
-          return 1 - (this.treasureFadeOutProgress.value - 50) / 50
-        }
-      } else {
-        return 0
-      }
-    },
-    maskOpacity() {
-      if (this.tourState === 0) {
-        if (this.treasureFadeInProgress.value > 50) {
-          return 1
-        } else {
-          return this.treasureFadeInProgress.value / 50
-        }
-      } else if (this.tourState === 1) {
-        return 1
-      } else if (this.tourState === 2) {
-        return (100 - this.treasureFadeOutProgress.value) / 100
-      } else {
-        return 0
-      }
-    },
-  },
   watch: {
-    'treasureFadeInProgress.value': {
-      handler(vNew, vOld) {
-        if (this.treasureFadeInProgress.value > 50) {
-          this.$refs.treasure.style.left = this.treasureFadeInInitialLeft + (this.treasureFadeInProgress.value - 50) / 50 * (this.treasureFadeInFinalLeft - this.treasureFadeInInitialLeft) + 'px'
-          this.$refs.treasure.style.top = this.treasureFadeInInitialTop + (this.treasureFadeInProgress.value - 50) / 50 * (this.treasureFadeInFinalTop - this.treasureFadeInInitialTop) + 'px'
-          this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(${1 + (this.treasureFadeInProgress.value - 50) / 50 * (this.treasureFinalHeightPercent / this.treasureInitialHeightPercent - 1)})`
-        } else {
-          this.$refs.treasure.style.left = this.treasureFadeInInitialLeft + 'px'
-          this.$refs.treasure.style.top = this.treasureFadeInInitialTop + 'px'
-          this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(1)`
-        }
-      }
-    },
     translateLength: {
       handler(vNew, vOld) {
         this.paperPositionLeft = `${vNew * landscapeSpeedRate}px`
@@ -306,33 +203,10 @@ export default {
     },
   },
   mounted() {
-    const that = this
     this.animationFrameId = requestAnimationFrame(this.animationFrameTask)
-
-    this.treasureFadeInTween = new TWEEN.Tween(that.treasureFadeInProgress)
-    this.treasureFadeInTween.to({
-      value: 100,
-    }, treasureFadeInDuration)
-    this.treasureFadeInTween.onComplete(() => {
-      this.tourState = 1
-      this.$refs['treasure-serial-frames'].play()
-    })
-    this.treasureFadeInTween.easing(TWEEN.Easing.Linear.None)
-
-    this.treasureFadeOutTween = new TWEEN.Tween(that.treasureFadeOutProgress)
-    this.treasureFadeOutTween.to({
-      value: 100,
-    }, treasureFadeOutDuration)
-    this.treasureFadeOutTween.onComplete(() => {
-      this.tourState = 3
-    })
-    this.treasureFadeOutTween.easing(TWEEN.Easing.Linear.None)
-
   },
   unmounted() {
     cancelAnimationFrame(this.animationFrameId)
-    this.treasureFadeInTween.stop()
-    this.treasureFadeOutTween.stop()
     clearInterval(this.treasureFrameIntervalId)
   },
   methods: {
@@ -359,7 +233,7 @@ export default {
       }
     },
     onWheel(e) {
-      if (this.tourState === 3) {
+      if (this.canMoveCamera) {
         this.translateLength -= e.deltaY
         if (this.translateLength > 0) {
           this.translateLength = 0
@@ -387,7 +261,7 @@ export default {
       }
 
       // 根据速度更新距离
-      if (this.tourState === 3) {
+      if (this.canMoveCamera) {
         this.translateLength += this.moveSpeed * timeElapsed
         if (this.translateLength > 0) {
           this.translateLength = 0
@@ -397,8 +271,6 @@ export default {
         }
       }
 
-      TWEEN.update()
-
       this.lastAnimationTimeStamp = timeStamp
       this.animationFrameId = requestAnimationFrame(this.animationFrameTask)
     },
@@ -418,45 +290,7 @@ export default {
       }
     },
     onClickPeopleNearHotSpot() {
-      if (this.tourState !== 3) {
-        return
-      }
-
-      // 计算淡入过渡的起始终止位置
-      this.treasureFadeInInitialLeft = this.$refs.treasure.offsetLeft
-      this.treasureFadeInInitialTop = this.$refs.treasure.offsetTop
-      this.treasureFadeInFinalLeft = window.innerWidth / 2 - this.$refs['people-near-wrap'].offsetLeft
-      this.treasureFadeInFinalTop = window.innerHeight / 2 - this.$refs['people-near-wrap'].offsetTop
-
-      // 计算淡入过渡终止时文物需要放大的倍数
-      const viewportRatio = window.innerWidth / window.innerHeight
-      const frameRatio = treasureFrameWidth / treasureFrameHeight
-      if (viewportRatio >= frameRatio) { // 视口比序列帧扁,序列帧高度会缩放到和视口相同,所以序列帧的第一帧作为淡入时的文物图片,也缩放到和视口等高即可。
-        this.treasureFinalHeightPercent = 100
-      } else { // 视口比序列帧窄,序列帧宽度会缩放到和视口相同,所以序列帧的第一帧作为淡入时的文物图片,也缩放到和视口等宽即可。
-        const treasureFinalHeight = treasureFrameHeight / treasureFrameWidth * window.innerWidth
-        this.treasureFinalHeightPercent = treasureFinalHeight / window.innerHeight * 100
-      }
-
-      this.tourState = 0
-      this.treasureFadeInTween.start()
-    },
-    onClickStopTreasureDisplay() {
-      this.tourState = 3
-      this.treasureFadeInTween.stop()
-      this.treasureFadeOutTween.stop()
-      this.$refs['treasure-serial-frames'].stop()
-      if (this.$refs.treasure) {
-        this.$refs.treasure.style.left = this.treasureFadeInInitialLeft + 'px'
-        this.$refs.treasure.style.top = this.treasureFadeInInitialTop + 'px'
-        this.$refs.treasure.style.transform = `translate(-50%, -50%) scale(1)`
-      }
-    },
-    onTreasureFrameLoad(idx) {
-      this.treasureFrameStateList[idx] = true
-    },
-    onTreasureFrameError(idx) {
-      this.treasureFrameStateList[idx] = false
+      this.$refs['treasure-display'].startDisplay()
     },
   }
 }
@@ -540,12 +374,7 @@ export default {
       top: 29.5%;
       z-index: 3;
     }
-    > .treasure {
-      position: absolute;
-      left: 49%;
-      top: 32.5%;
-      z-index: 2;
-      transform: translate(-50%, -50%);
+    > .treasure-display {
     }
   }
   .introduce {
@@ -553,35 +382,6 @@ export default {
     top: 5%;
     width: 13.727%;
   }
-  .fade-mask {
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    background-color: #bfbfa7;
-    z-index: 1;
-    img {
-      width: 100%;
-      height: 100%;
-      object-fit: contain;
-    }
-  }
-  .btn-stop-treasure-display {
-    position: absolute;
-    top: 20px;
-    right: 20px;
-    z-index: 4;
-    border: none;
-  }
-  .treasure-serial-frames {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    z-index: 3;
-  }
   @media screen and (max-height: 810px) {
     .people-far-wrap {
       height: 600px;