vue.js3: 给滑动滚动添加上惯性(vue@3.2.26)
一,代码:
<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://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/06/01/vue-js3-gei-hua-dong-gun-dong-tian-jia-shang-guan-xing-vue/
对应的源码可以访问这里获取: 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