zl程序教程

您现在的位置是:首页 >  前端

当前栏目

vue.js3: 给滑动滚动添加上惯性(vue@3.2.26)

Vue 添加 滚动 滑动 26 3.2 js3 惯性
2023-09-14 09:01:18 时间

一,代码:

<template>
  <div
      class="wrapper"
      ref="wrapper"
      @touchstart.prevent="onStart"
      @touchmove.prevent="onMove"
      @touchend.prevent="onEnd"
      @touchcancel.prevent="onEnd"
      @mousedown.prevent="onStart"
      @mousemove.prevent="onMove"
      @mouseup.prevent="onEnd"
      @mousecancel.prevent="onEnd"
      @mouseleave.prevent="onEnd"
      @transitionend="onTransitionEnd">

    <div class="list" ref="scroller" :style="scrollerStyle">
      <div >
        <img src="static/image/01.jpg" style="width:100%;height:100vh;"/>
      </div>
      <div>
        <img src="static/image/back1.jpg" style="width:100%;height:100vh;"/>
      </div>
      <div>
        <img src="static/image/back2.jpg" style="width:100%;height:100vh;"/>
      </div>
      <div>
        <img src="static/image/back3.jpg" style="width:100%;height:100vh;"/>
      </div>
      <div>
        <img src="static/image/back4.png" style="width:100%;height:100vh;"/>
      </div>
    </div>
  </div>
</template>

<script>
import {ref,onMounted,nextTick,computed} from "vue"
export default {
  name: "Guanxing2",
  setup() {
    const wrapper = ref(null);
    const scroller = ref(null);

    const minY = ref(0);
    const maxY = ref(0);
    const wrapperHeight = ref(0);
    const offsetY = ref(0);
    const duration = ref(0);
    const bezier = ref('linear');
    const startY = ref(0);
    const pointY = ref(0);
    const startTime = ref(0);                 // 惯性滑动范围内的 startTime
    const momentumStartY = ref(0);            // 惯性滑动范围内的 startY
    const momentumTimeThreshold = ref(300);   // 惯性滑动的启动 时间阈值
    const momentumYThreshold = ref(15);       // 惯性滑动的启动 距离阈值
    const isStarted = ref(false);             // start锁
    
    onMounted(() => {
      nextTick(()=>{
        const { height: lwrapperHeight } = wrapper.value.getBoundingClientRect();
        const { height: lscrollHeight } = scroller.value.getBoundingClientRect();
        wrapperHeight.value = lwrapperHeight;
        console.log("wrapperHeight:"+lwrapperHeight);
        console.log("scrollHeight:"+lscrollHeight);
        minY.value = lwrapperHeight - lscrollHeight;
      })
    });
    
    const scrollerStyle = computed(()=> {
          return {
            'transform': `translate3d(0, ${offsetY.value}px, 0)`,
            'transition-duration': `${duration.value}ms`,
            'transition-timing-function': bezier.value,
          }
       }
    )
    //按下
    const onStart = (e)=>{
      const point = e.touches ? e.touches[0] : e;
      isStarted.value = true;
      duration.value = 0;
      stop();
      pointY.value = point.pageY;
      momentumStartY.value = startY.value = offsetY.value;
      startTime.value = new Date().getTime();
    }
    //移动
    const onMove = (e) => {
      if (!isStarted.value) return;
      const point = e.touches ? e.touches[0] : e;
      const deltaY = point.pageY - pointY.value;
      // 浮点数坐标会影响渲染速度
      let loffsetY = Math.round(startY.value + deltaY);
      // 超出边界时增加阻力
      if (loffsetY < minY.value || loffsetY > maxY.value) {
        loffsetY = Math.round(startY.value + deltaY / 3);
      }

      console.log("onMove:"+loffsetY);
      offsetY.value = loffsetY;
      const now = new Date().getTime();
      // 记录在触发惯性滑动条件下的偏移值和时间
      if (now - startTime.value > momentumTimeThreshold.value) {
        momentumStartY.value = offsetY.value;
        startTime.value = now;
      }
    }
    //放开
    const onEnd = (e) => {
      console.log(e);
      if (!isStarted.value) return;
      isStarted.value = false;
      console.log("onEnd:isNeedReset:begin");
      if (isNeedReset()) return;
      const absDeltaY = Math.abs(offsetY.value - momentumStartY.value);
      const lduration = new Date().getTime() - startTime.value;
      // 启动惯性滑动
      if (lduration < momentumTimeThreshold.value && absDeltaY > momentumYThreshold.value) {
        console.log("开始惯性滑动:"+offsetY.value);

        const lmomentum = momentum(offsetY.value, momentumStartY.value, lduration);
        offsetY.value = Math.round(lmomentum.destination);
        duration.value = lmomentum.duration;
        bezier.value = lmomentum.bezier;
      }
    }
    //过渡结束后的监听
    const onTransitionEnd = () => {
      isNeedReset();
    }
    //计算得到惯性的值
    const momentum = (current, start, duration) => {
      const durationMap = {
        'noBounce': 2500,
        'weekBounce': 800,
        'strongBounce': 400,
      };
      const bezierMap = {
        'noBounce': 'cubic-bezier(.17, .89, .45, 1)',
        'weekBounce': 'cubic-bezier(.25, .46, .45, .94)',
        'strongBounce': 'cubic-bezier(.25, .46, .45, .94)',
      };
      let type = 'noBounce';
      // 惯性滑动加速度
      const deceleration = 0.003;
      // 回弹阻力
      const bounceRate = 10;
      // 强弱回弹的分割值
      const bounceThreshold = 300;
      // 回弹的最大限度
      const maxOverflowY = wrapperHeight.value / 6;
      let overflowY;

      const distance = current - start;
      const speed = 2 * Math.abs(distance) / duration;
      let destination = current + speed / deceleration * (distance < 0 ? -1 : 1);
      if (destination < minY.value) {
        overflowY = minY.value - destination;
        type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
        destination = Math.max(minY.value - maxOverflowY, minY.value - overflowY / bounceRate);
      } else if (destination > maxY.value) {
        overflowY = destination - maxY.value;
        type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
        destination = Math.min(maxY.value + maxOverflowY, maxY.value + overflowY / bounceRate);
      }

      return {
        destination,
        duration: durationMap[type],
        bezier: bezierMap[type],
      };
    }
    // 超出边界时需要重置位置
    const isNeedReset = () => {
      let loffsetY;
      //console.log("offsetY:"+offsetY.value+";minY:"+minY.value+";maxY:"+maxY.value)
      if (offsetY.value < minY.value) {
        loffsetY = minY.value;
      } else if (offsetY.value > maxY.value) {
        loffsetY = maxY.value;
      }
      if (typeof loffsetY !== 'undefined') {
        console.log("isNeedReset:"+loffsetY);
        offsetY.value = loffsetY;
        duration.value = 500;
        bezier.value = 'cubic-bezier(.165, .84, .44, 1)';
        return true;
      }
      return false;
    }
    //stop
    const stop = () => {
      // 获取当前 translate 的位置
      const matrix = window.getComputedStyle(scroller.value).getPropertyValue('transform');
      offsetY.value = Math.round(+matrix.split(')')[0].split(', ')[5]);
    }


    return {
      //------变量
      wrapper,
      scroller,
      minY,
      maxY,
      wrapperHeight,
      offsetY,
      duration,
      bezier,
      startY,
      pointY,
      startTime,                 // 惯性滑动范围内的 startTime
      momentumStartY,            // 惯性滑动范围内的 startY
      momentumTimeThreshold,   // 惯性滑动的启动 时间阈值
      momentumYThreshold,       // 惯性滑动的启动 距离阈值
      isStarted,             // start锁
      //方法
      onStart,
      onMove,
      onEnd,
      onTransitionEnd,
      momentum,
      isNeedReset,
      stop,
      //computed
      scrollerStyle,
    };
  },

}
</script>

<style scoped>
body, ul {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
}

.wrapper {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin: 0 auto;
  height: 100%;
  width: 100%;
  border: 0px solid #000;
  overflow: hidden;
}

.list {
  background-color: #70f3b7;
}

.list-item {
  height: 40px;
  line-height: 40px;
  width: 100%;
  text-align: center;
  border-bottom: 1px solid #ccc;
}
</style>

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

         对应的源码可以访问这里获取: https://github.com/liuhongdi/
         或: https://gitee.com/liuhongdi

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,查看效果

三,查看vue的版本:

liuhongdi@lhdpc:/data/vue/demo1$ npm list vue
demo1@0.1.0 /data/vue/demo1
├─┬ @vue/cli-plugin-babel@4.5.15
│ └─┬ @vue/babel-preset-app@4.5.15
│   └── vue@3.2.26 deduped
├─┬ element-plus@1.2.0-beta.6
│ ├─┬ @element-plus/icons-vue@0.2.4
│ │ └── vue@3.2.26 deduped
│ ├─┬ @vueuse/core@7.4.1
│ │ ├─┬ @vueuse/shared@7.4.1
│ │ │ └── vue@3.2.26 deduped
│ │ ├─┬ vue-demi@0.12.1
│ │ │ └── vue@3.2.26 deduped
│ │ └── vue@3.2.26 deduped
│ └── vue@3.2.26 deduped
└─┬ vue@3.2.26
  └─┬ @vue/server-renderer@3.2.26
    └── vue@3.2.26 deduped

四,参考来源:

https://blog.csdn.net/qq_41903941/article/details/106679904