如何让列表滑个不停?仿抖音无限滚动与无限加载组件完整拆解
2026/9/15 17:45:39 网站建设 项目流程

如何让列表滑个不停?仿抖音无限滚动与无限加载组件完整拆解

【免费下载链接】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。参数与事件就这几样:

参数类型说明
apiFunction数据请求函数,组件传入pageNo/pageSize,你返回{ success, data: { list, total } }
loadingBoolean请求进行中,列表底部出现加载指示
fullLoadingBoolean为 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询