专栏名称: 程序员大咖
程序员大咖,努力成就期待着的自己。分享程序员技术文章、程序员工具资源、程序员精选课程、程序员视频教程、程序员热点资讯、程序员学习资料等。
目录
相关文章推荐
51好读  ›  专栏  ›  程序员大咖

给你的 H5 页面加上惯性滚动吧!

程序员大咖  · 公众号  ·  · 2025-01-20 10:24

正文

在移动端,如果你使用过 overflow: scroll 生成一个滚动容器,会发现它的滚动是比较卡顿,呆滞的。为什么会出现这种情况呢?


因为我们早已习惯了目前的主流操作系统和浏览器视窗的滚动体验,比如滚动到边缘会有回弹,手指停止滑动以后还会按惯性继续滚动一会,手指快速滑动时页面也会快速滚动。而这种原生滚动容器却没有,就会让人感到卡顿。


首先,让我们来看一下它是怎样让滚动更流畅的吧。

<html>  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Documenttitle>  head>  <body>    <div id="app">div>    <template id="tpl">      <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"      >        <ul class="list" ref="scroller" :style="scrollerStyle">          <li class="list-item" v-for="item in list">{{item}}li>        ul>      div>    template>    <style>      body,      ul {        margin: 0;        padding: 0;      }
ul { list-style: none; }
.wrapper { width: 100vw; height: 100vh; overflow: hidden; }
.list { background-color: #70f3b7; }
.list-item { height: 40px; line-height: 40px; width: 100%; text-align: center; border-bottom: 1px solid #ccc; }style> <script src="https://cdn.jsdelivr.net/npm/vue@2">script>
<script>
new Vue({ el: "#app", template: "#tpl", computed: { list() { let list = []; for (let i = 0; i < 100; i++) { list.push(i); } return list; }, scrollerStyle() { return { transform: `translate3d(0, ${this.offsetY}px, 0)`, "transition-duration": `${this.duration}ms`, "transition-timing-function": this.bezier, }; }, }, data() { return { minY: 0, maxY: 0, wrapperHeight: 0, duration: 0, bezier: "linear", pointY: 0, // touchStart 手势 y 坐标 startY: 0, // touchStart 元素 y 偏移值 offsetY: 0, // 元素实时 y 偏移值 startTime: 0, // 惯性滑动范围内的 startTime momentumStartY: 0, // 惯性滑动范围内的 startY momentumTimeThreshold: 300, // 惯性滑动的启动 时间阈值 momentumYThreshold: 15, // 惯性滑动的启动 距离阈值 isStarted: false, // start锁 }; }, mounted() { this.$nextTick(() => { this.wrapperHeight = this.$refs.wrapper.getBoundingClientRect().height; this.minY = this.wrapperHeight - this.$refs.scroller.getBoundingClientRect().height; }); }, methods: { onStart(e) { const point = e.touches ? e.touches[0] : e; this.isStarted = true; this.duration = 0; this.stop(); this.pointY = point.pageY; this.momentumStartY = this.startY = this.offsetY; this.startTime = new Date().getTime(); }, onMove(e) { if (!this.isStarted) return; const point = e.touches ? e.touches[0] : e; const deltaY = point.pageY - this.pointY; this.offsetY = Math.round(this.startY + deltaY); const now = new Date().getTime(); // 记录在触发惯性滑动条件下的偏移值和时间 if (now - this.startTime > this.momentumTimeThreshold) { this.momentumStartY = this.offsetY; this.startTime = now; } }, onEnd(e) { if (!this.isStarted) return; this.isStarted = false; if (this.isNeedReset()) return; const absDeltaY = Math.abs(this.offsetY - this.momentumStartY); const duration = new Date().getTime() - this.startTime; // 启动惯性滑动 if ( duration < this.momentumTimeThreshold && absDeltaY > this.momentumYThreshold ) { const momentum = this.momentum( this.offsetY, this.momentumStartY, duration ); this.offsetY = Math.round(momentum.destination); this.duration = momentum.duration; this.bezier = momentum.bezier; } }, onTransitionEnd() { this.isNeedReset(); }, 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






请到「今天看啥」查看全文