Jelajahi Sumber

feat: move speed

chenlei 1 tahun lalu
induk
melakukan
35f742054e
1 mengubah file dengan 164 tambahan dan 45 penghapusan
  1. 164 45
      src/views/BambooHotView2/index.vue

+ 164 - 45
src/views/BambooHotView2/index.vue

@@ -49,7 +49,10 @@
           hide: hotVisible && checkedHotId !== 2
         }"
       >
-        <img src="./images/bamboo2.png">
+        <img
+          src="./images/bamboo2.png"
+          @load="handleBambooOffset(2)"
+        >
       </div>
       <div
         class="bamboo-hot2__hot b2"
@@ -67,7 +70,10 @@
           hide: hotVisible && checkedHotId !== 3
         }"
       >
-        <img src="./images/bamboo3.png">
+        <img
+          src="./images/bamboo3.png"
+          @load="handleBambooOffset(3)"
+        >
 
         <div
           class="bamboo-hot2__hot"
@@ -86,7 +92,10 @@
           hide: hotVisible && checkedHotId !== 4
         }"
       >
-        <img src="./images/bamboo4.png">
+        <img
+          src="./images/bamboo4.png"
+          @load="handleBambooOffset(4)"
+        >
 
         <div
           class="bamboo-hot2__hot"
@@ -132,7 +141,10 @@
           hide: hotVisible && checkedHotId !== 8
         }"
       >
-        <img src="./images/bamboo8.png">
+        <img
+          src="./images/bamboo8.png"
+          @load="handleBambooOffset(8)"
+        >
 
         <div
           class="bamboo-hot2__hot"
@@ -199,6 +211,7 @@
         class="bamboo-hot2-bg-wrap"
       >
         <img
+          v-if="bgImgLoaded"
           class="bamboo-hot2__grass"
           :class="{
             hide: hotVisible
@@ -209,6 +222,10 @@
         <img
           class="bamboo-hot2__bg"
           src="./images/bg.png"
+          :style="{
+            filter: hotVisible ? 'saturate(1.3) brightness(0.95)' : 'none'
+          }"
+          @load="bgImgLoaded = true"
         >
       </div>
     </div>
@@ -226,16 +243,16 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, watch, onBeforeUnmount } from 'vue'
 import { useRouter } from 'vue-router'
 import useSizeAdapt from "@/useFunctions/useSizeAdapt"
 
-const ITEM_SCROLL_MAP = {
-  1: 10,
+let itemScrollMap = {
+  1: 0,
   2: 166,
-  3: 231,
-  4: 420,
-  8: 958
+  3: 201,
+  4: 450,
+  8: 1048
 }
 const ITEM_INFO_MAP = {
   1: {
@@ -281,13 +298,13 @@ const ITEM_INFO_MAP = {
   },
 }
 
+const bgImgLoaded = ref(false)
 const {
   windowSizeInCssForRef,
   windowSizeWhenDesignForRef,
 } = useSizeAdapt()
 const router = useRouter()
-let startX = 0
-let lastStartX = 0
+
 const bambooWrap = ref()
 const bambooWrapBg = ref()
 
@@ -298,42 +315,126 @@ const handleHot = (id) => {
   checkedHotId.value = id
   hotVisible.value = true
 
+  cancelAnimationFrame(animationFrameId.value)
   bambooWrap.value.scrollTo({
-    left: ITEM_SCROLL_MAP[id],
+    left: itemScrollMap[id],
     behavior: 'smooth'
   })
-
-  lastStartX = ITEM_SCROLL_MAP[id]
+  translateX.value = itemScrollMap[id]
 }
 
-const handleTouchstart = (v) => {
-  if (hotVisible.value) return
-
-  startX = v.changedTouches[0].pageX
+const handleBambooOffset = (target) => {
+  const offset = window.innerWidth / 6
+  const left = document.getElementsByClassName(`bamboo-hot2-b${target}`)?.[0].getBoundingClientRect().left
+  let temp = 0
+  switch (target) {
+  case 3:
+    temp = window.innerWidth / 3
+    break
+  case 4:
+    temp = -(window.innerWidth * 0.25)
+    break
+  case 8:
+    temp = -(window.innerWidth * 0.7)
+    break
+  }
+  itemScrollMap[target] = left - offset + temp
 }
-const handleTouchmove = (v) => {
-  v.preventDefault()
-  if (hotVisible.value) return
 
-  const wrapWidth = bambooWrapBg.value.scrollWidth - window.innerWidth
-  const moveX = startX - v.changedTouches[0].pageX
+// 动画帧相关
+const lastAnimationTimeStamp = ref(0)
+const animationFrameId = ref(0)
+const moveSpeed = ref(0)
+const translateX = ref(0)
+const maxTranslateXLength = ref(0)
+const lastMoveEventTimeStamp = ref(0)
+const isMouseDown = ref(false)
+const isMove = ref(false)
+const lastTouchPos = ref(0)
+
+watch([bambooWrapBg, bgImgLoaded], () => {
+  if (!bgImgLoaded.value) return
+
+  maxTranslateXLength.value = bambooWrapBg.value ? bambooWrapBg.value.scrollWidth - window.innerWidth : 0
+  animationFrameId.value = requestAnimationFrame(animationFrameTask)
+})
+
+const animationFrameTask = () => {
+  const timeStamp = Date.now()
+  const timeElapsed = timeStamp - lastAnimationTimeStamp.value
+
+  // 速度减慢
+  if (moveSpeed.value > 0) {
+    moveSpeed.value -= 0.003 * timeElapsed
+    if (moveSpeed.value < 0) {
+      moveSpeed.value = 0
+    }
+  } else if (moveSpeed.value < 0) {
+    moveSpeed.value += 0.003 * timeElapsed
+    if (moveSpeed.value > 0) {
+      moveSpeed.value = 0
+    }
+  }
+
+  // 根据速度更新距离
+  translateX.value += moveSpeed.value * timeElapsed
+  if (translateX.value < 0) {
+    translateX.value = 0
+  } else if (translateX.value > maxTranslateXLength.value) {
+    translateX.value = maxTranslateXLength.value
+    moveSpeed.value = 0
+  }
 
-  bambooWrap.value.scrollTo({
-    left: Math.min(moveX + lastStartX, wrapWidth),
+  bambooWrap.value?.scrollTo({
+    left: translateX.value,
     behavior: 'instant'
   })
+  lastAnimationTimeStamp.value = timeStamp
+  animationFrameId.value = requestAnimationFrame(animationFrameTask)
+}
+
+const handleTouchstart = (e) => {
+  if (hotVisible.value) return
+
+  isMouseDown.value = true
+  moveSpeed.value = 0
+  lastMoveEventTimeStamp.value = 0
+  lastAnimationTimeStamp.value = Date.now()
+  lastTouchPos.value = e.changedTouches[0].clientX
+}
+const handleTouchmove = (e) => {
+  e.preventDefault()
+  if (hotVisible.value || !isMouseDown.value || !e.changedTouches.length) return
+
+  if (
+    lastMoveEventTimeStamp.value &&
+    e.timeStamp - lastMoveEventTimeStamp.value > 1
+  ) {
+    // 更新speed
+    isMove.value = true
+    const currentMoveSpeed =
+      (-(e.changedTouches[0].clientX - lastTouchPos.value) /
+        (e.timeStamp - lastMoveEventTimeStamp.value)) *
+      1.5
+    moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
+    lastTouchPos.value = e.changedTouches[0].clientX
+  }
+  lastMoveEventTimeStamp.value = e.timeStamp
 }
-const handleTouchend = (v) => {
+const handleTouchend = () => {
   if (hotVisible.value) return
 
-  const wrapWidth = bambooWrapBg.value.scrollWidth - window.innerWidth
-  lastStartX = Math.min(Math.max(startX - v.changedTouches[0].pageX + lastStartX, 0), wrapWidth)
+  isMouseDown.value = false
+  setTimeout(() => {
+    isMove.value = false
+  })
 }
 
 const goBack = () => {
   if (hotVisible.value) {
     hotVisible.value = false
     checkedHotId.value = 0
+    animationFrameId.value = requestAnimationFrame(animationFrameTask)
     return
   }
 
@@ -344,6 +445,10 @@ const goBack = () => {
     }
   })
 }
+
+onBeforeUnmount(() => {
+  cancelAnimationFrame(animationFrameId.value)
+})
 </script>
 
 <style lang="less" scoped>
@@ -357,6 +462,7 @@ img {
 
 .hide {
   opacity: 0 !important;
+  animation: none !important;
 }
 
 [class^="bamboo-hot2-b"] {
@@ -429,6 +535,7 @@ img {
     font-size: 12px;
     font-family: KaiTi;
     writing-mode: vertical-rl;
+    animation: breathing linear 2s infinite;
 
     &::before {
       content: '';
@@ -441,7 +548,7 @@ img {
   }
   &-b1 {
     left: calc(50 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
 
     .bamboo-hot2__hot {
       top: calc(340 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
@@ -450,18 +557,18 @@ img {
   }
   &-b2 {
     left: calc(250 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
     z-index: 2;
   }
   .bamboo-hot2__hot.b2 {
     top: calc(200 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
     left: calc(305 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
     z-index: 4;
   }
   &-b3 {
-    left: calc(155 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(5px);
+    left: calc(140 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
+    transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
 
     .bamboo-hot2__hot {
       top: calc(300 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
@@ -470,7 +577,7 @@ img {
   }
   &-b4 {
     left: calc(600 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(5px);
+    transform: translateZ(calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.85);
 
     .bamboo-hot2__hot {
       top: calc(220 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
@@ -479,20 +586,20 @@ img {
   }
   &-b5 {
     left: calc(1050 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
   }
   &-b6 {
     left: calc(1170 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(15px);
+    transform: translateZ(calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
   }
   &-b7 {
     left: calc(1050 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
     z-index: 2;
   }
   &-b8 {
     left: calc(1180 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(5px);
+    transform: translateZ(calc(25 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.8);
 
     .bamboo-hot2__hot {
       top: calc(320 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
@@ -501,30 +608,30 @@ img {
   }
   &-b9 {
     left: calc(1480 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(20 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.82);
   }
   &-b10 {
     left: calc(1810 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(10 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
   }
   &-b11 {
     left: calc(1790 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(5px);
+    transform: translateZ(calc(5 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
     z-index: 2;
   }
   &-b12 {
     left: calc(1600 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(15px);
+    transform: translateZ(calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
     z-index: 2;
   }
   &-b13 {
     left: calc(2050 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(15px);
+    transform: translateZ(calc(15 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef')));
     z-index: 2;
   }
   &-b14 {
     left: calc(2220 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'));
-    transform: translateZ(10px);
+    transform: translateZ(calc(30 /v-bind('windowSizeWhenDesignForRef')* v-bind('windowSizeInCssForRef'))) scale(0.7);
   }
   &.wrap-hide {
     &::before,
@@ -589,4 +696,16 @@ img {
     transition: opacity 0.5s ease-in-out;
   }
 }
+
+@keyframes breathing {
+  0% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.3;
+  }
+  100% {
+    opacity: 1;
+  }
+}
 </style>