如何让列表滑个不停?仿抖音无限滚动与无限加载组件完整拆解
【免费下载链接】douyinVue3 + Pinia 仿抖音,Vue 在移动端的最佳实践 . Imitate TikTok ,Vue Best practices on Mobile项目地址: https://gitcode.com/GitHub_Trending/do/douyin
手指向下一滑,新视频已经备好,这是短视频流的核心体验。开源项目 douyin 用 Vue 3 复刻了这套无限滚动与下拉刷新,把无限加载封装成可复用的组件。
先让无限列表组件跑起来 🧩
ScrollList 负责接管数据,你只需把请求函数交给它,渲染交给插槽:
<ScrollList :api="recommendedShop"> <template #default="{ list }"> <ShopItem v-for="item in list" :key="item.id" :good="item" /> </template> </ScrollList>这段最小用法取自 src/pages/shop/Shop.vue,组件本体在 src/components/ScrollList.vue。参数与事件就这几样:
| 参数 | 类型 | 说明 |
|---|---|---|
api | Function | 数据请求函数,组件传入pageNo/pageSize,你返回{ success, data: { list, total } } |
loading | Boolean | 请求进行中,列表底部出现加载指示 |
fullLoading | Boolean | 为 true 时加载指示铺满全屏,用于首屏 |
| 事件 | 触发时机 |
|---|---|
pulldown | 滚动位置接近底部,请求下一页 |
refresh | 在顶部下拉越过阈值,重置重新拉取 |
实际跑起来是这个效果:
分页与加载锁:数据是怎么"续"上来的
src/components/ScrollList.vue 里四个状态字段管着整条加载链路:list是已经渲染出来的数据;pageNo记录当前到第几页;total是服务端报出的总条数,用来判断"还有没有下一页";loading标记是否正有请求在飞。
loading就是加载锁,像电梯里的关门按钮:按下一瞬间门开始关,在关完之前再按都无效。请求进行中,后来的pulldown会被if (state.loading) return直接拦下,你再怎么快速滑动,也不会有第二个请求发出去。
什么时候该加载下一页?组件不等你滑到底,而是提前 60px 就触发:
if (this.wrapper.scrollHeight - this.wrapper.clientHeight < this.wrapper.scrollTop + 60) { this.$emit('pulldown') }出处是 src/components/Scroll.vue 的 scroll 方法。为什么提前一点?请求需要时间,真滑到底再发,用户就得盯着加载圈干等;提前发,手指触底时数据往往已经到位了。
数据回来后用的是state.list.concat(res.data.list)增量拼接,而不是整表替换。增量追加让 Vue 的 diff 只比对新增节点,已有 DOM 原封不动;整表替换则每条都要重新走一遍协调,更新量差距明显。
触摸事件与下拉刷新:手指那头发生了什么
数据续上来了,再看手指那头。src/components/Scroll.vue 的三个方法构成了下拉刷新:touchmove时,只有scrollTop为 0 才记下触摸起点startMoveY,之后每帧用当前pageY减起点算出位移,再经distance - 40 < 10 ? distance - 40 : 10把视觉位移钳制在 10px 以内——拉再狠,画面也只让出 10px;touchend时,只有"一开始就在顶部"且"确实拖过"(distance 非空且scrollTop === 0)才emit('refresh'),列表不在顶部时怎么拉都不会误触发。
松手后的回弹靠 transform 位移加一段 300ms 过渡完成:
'transition-duration': this.refresh ? '300ms' : '0ms', transform: `translate3d(0px, ${this.distance !== null ? this.distance : -40}px, 0)`这 10px 的位移加 300ms 的缓回,就是下拉时"内容有跟手指走、又弹回来"的体感来源:
加载状态:让用户"看见"系统在干活 👀
无限加载中,最糟的体验是用户不知道发生了什么,所以组件对三种时机各有交代:首屏用full-loading(由!state.list.length推得)显示全屏 Loading;滚动加载时loading为 true,列表底部出现小的加载指示;拉完则当total !== 0 && total === list.length时显示 NoMore,明确告诉用户"没有更多了"。
下拉时的物理反馈也是同一思路:一点点看得见的位移,等于一句"你的手势我收到了"。
从无限加载到虚拟滚动:再往前一步 🚀
scroll事件触发频率极高,好在有加载锁兜底——pulldown触发多少次,最多只放行一次请求,状态锁本身就完成了频率控制,不必再额外写节流。
当列表长到几百个 DOM 节点开始拖慢渲染,下一步就是虚拟滚动:只渲染视口内的条目,可以用IntersectionObserver监听条目进出可视区来增删节点。项目已预留 src/components/WaterfallList.vue,它把列表按奇偶下标拆成两列,可直接嵌进 ScrollList 做瀑布流无限加载:
另有两个值得做的方向:给 Scroll 加方向开关支持横向滚动;getData失败分支目前只弹"查询失败"提示,加个重试按钮,容错会更完整。
调参与避坑清单 📋
| 项 | 建议 |
|---|---|
| 预加载阈值 | 默认 60px 是折中值,弱网可上调到 100~200,强网降到 30 |
pageSize | 默认 10 条,偏大增加首屏渲染成本,偏小请求更频繁,按条目高度权衡 |
fixedHeight | 需要把滚动位置同步给外部 UI 时传具体数值(px),否则保持默认 -1 |
useRefresh | 为 true 才进入绑定触摸事件的模板分支,用不到就别开 |
| 刷新语义 | refresh会把pageNo归零并整表替换,和"追加加载"是两条路径,别混用 |
把"Scroll 管手势、ScrollList 管数据"拆开后,无限加载就从一次性实现变成了可复用能力:换一个api,任何列表场景都能接上。想深入细节,可以git clone https://gitcode.com/GitHub_Trending/do/douyin拉取完整源码,对照首页视频流与 src/pages/shop/ 商城推荐两个场景一起读。
【免费下载链接】douyinVue3 + Pinia 仿抖音,Vue 在移动端的最佳实践 . Imitate TikTok ,Vue Best practices on Mobile项目地址: https://gitcode.com/GitHub_Trending/do/douyin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考