Vant PullRefresh 下拉刷新组件完全指南:从基础用法到源码级原理解析
2026/9/12 21:09:52 网站建设 项目流程

Vant PullRefresh 下拉刷新组件完全指南:从基础用法到源码级原理解析

【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant

导读

PullRefresh是 Vant 移动端组件库中用于提供「下拉刷新」交互的核心组件,广泛用于列表页、内容流页面的数据手动刷新场景。本文以 pull-refresh 组件文档 为主体,结合组件源码、样式定义与单元测试,系统讲解其引入方式、基础用法、成功提示、自定义插槽、完整 API 参考与主题定制方案,并深入剖析其「顶部检测—阻尼回弹—状态机驱动」的底层实现原理,帮助你在项目中快速落地并深入理解这一交互组件。

组件简介与引入方式

PullRefresh通过「手指在页面顶部下拉—释放触发刷新—加载完成回弹」的完整手势链路,为移动端列表提供刷新交互。它的实现文件位于 PullRefresh.tsx,样式位于 index.less,并在 index.ts 中通过withInstall包装后对外导出,同时注册了全局组件VanPullRefresh

推荐通过app.use进行全局注册:

import { createApp } from 'vue'; import { PullRefresh } from 'vant'; const app = createApp(); app.use(PullRefresh);

更多注册方式(局部注册、按需引入等)可参考 组件注册。

基础用法:v-model 与 refresh 事件

下拉刷新时组件会触发refresh事件。在事件的回调函数中可以执行同步或异步的数据请求操作,操作完成后需要将v-model设置为false,表示加载完成,组件才会收起头部并恢复初始状态。

<van-pull-refresh v-model="loading" @refresh="onRefresh"> <p>刷新次数: {{ count }}</p> </van-pull-refresh>
import { ref } from 'vue'; import { showToast } from 'vant'; export default { setup() { const count = ref(0); const loading = ref(false); const onRefresh = () => { setTimeout(() => { showToast('刷新成功'); loading.value = false; count.value++; }, 1000); }; return { count, loading, onRefresh, }; }, };

需要注意两点:

  • v-model绑定的loading受控状态:组件触发refresh后会将modelValue置为true,业务侧必须手动改回false才能结束加载动画。从源码看,onTouchEnd中会先emit('update:modelValue', true),再通过nextTick派发refresh事件,确保v-model值变化能被业务侧的watch及时感知(见 PullRefresh.tsx)。
  • refresh事件没有任何回调参数,刷新后的数据获取逻辑完全由业务侧自行编排。

官方演示页 demo/index.vue 中还展示了一个细节:刷新过程中调用showToast('刷新成功')与通过success-text展示成功提示是两种互斥的反馈方式,实际项目中可以按交互规范二选一。

成功提示:success-text 与 success-duration

通过success-text可以设置刷新成功后的顶部提示文案。当v-model被业务侧置为false时,组件会先展示「成功」状态,停留success-duration毫秒后再收起头部。

<van-pull-refresh v-model="isLoading" success-text="刷新成功" @refresh="onRefresh" > <p>刷新次数: {{ count }}</p> </van-pull-refresh>

该流程对应源码中的showSuccessTip方法:设置status = 'success',并在successDuration毫秒后通过setStatus(0)复位(见 PullRefresh.tsx)。success-duration默认值为500(毫秒),可传入字符串形式的数字,例如success-duration="800"

需要说明的是:只有配置了success-textsuccess插槽时,成功提示阶段才会出现;否则v-model置为false后组件直接回到normal状态。

自定义提示:五个状态插槽

通过插槽可以完全自定义下拉刷新过程中的提示内容。组件共暴露 5 个头部状态插槽:normalpullingloosingloadingsuccess,其中pullingloosingloading会接收{ distance }插槽参数,可用于实现随下拉距离缩放的动画效果。

<van-pull-refresh v-model="isLoading" :head-height="80" @refresh="onRefresh"> <!-- 下拉提示,通过 scale 实现一个缩放效果 --> <template #pulling="{ distance }"> <img class="doge" src="https://fastly.jsdelivr.net/npm/@vant/assets/doge.png" :style="{ transform: `scale(${distance / 80})` }" /> </template> <!-- 释放提示 --> <template #loosing> <img class="doge" src="https://fastly.jsdelivr.net/npm/@vant/assets/doge.png" /> </template> <!-- 加载提示 --> <template #loading> <img class="doge" src="https://fastly.jsdelivr.net/npm/@vant/assets/doge-fire.jpeg" /> </template> <p>刷新次数: {{ count }}</p> </van-pull-refresh> <style> .doge { width: 140px; height: 72px; margin-top: 8px; border-radius: 4px; } </style>

官方 Demo 中通过cdnURL('doge.png')预加载图片并基于distance / headHeight计算缩放比例(见 demo/index.vue)。实际项目中应替换为业务自己的图片资源,并建议同样在onMounted阶段预加载,避免下拉时图片闪烁。

在渲染逻辑上(renderStatus,见 PullRefresh.tsx),优先级为:插槽 > 内置文案。即某个状态下定义了插槽,就渲染插槽内容;未定义插槽时,pulling/loosing/success渲染纯文本,loading渲染 Loading 组件 加加载文案。

状态机与触发原理:源码级解析

从源码结构看,PullRefresh的交互本质是一个五状态机,由normal → pulling → loosing → loading → success流转(见 PullRefresh.tsx):

状态含义进入条件
normal静止状态distance === 0
pulling正在下拉0 < distance < pullDistance
loosing已超过阈值、可释放distance >= pullDistance
loading刷新加载中loosing状态松手
success成功提示加载完成后且配置了成功提示

状态由setStatus方法统一驱动,并同步派发change事件(见 PullRefresh.tsx):

const setStatus = (distance: number, isLoading?: boolean) => { const pullDistance = +(props.pullDistance || props.headHeight); state.distance = distance; if (isLoading) { state.status = 'loading'; } else if (distance === 0) { state.status = 'normal'; } else if (distance < pullDistance) { state.status = 'pulling'; } else { state.status = 'loosing'; } emit('change', { status: state.status, distance }); };

触发条件:父级滚动容器在顶部

PullRefresh的核心触发条件是「父级滚动元素的滚动条在顶部位置」。checkPosition通过getScrollTop(scrollParent.value!) === 0判断是否到达顶部(见 PullRefresh.tsx),只有到达顶部时才记录触摸起点并允许下拉。滚动容器由useScrollParent(root)自动向上查找确定,它可能是window,也可能是最近的overflow: auto/scroll元素。

阻尼回弹算法

为了让下拉超过阈值后手感更「有阻力」,源码中实现了分段阻尼算法ease(见 PullRefresh.tsx):

const ease = (distance: number) => { const pullDistance = +(props.pullDistance || props.headHeight); if (distance > pullDistance) { if (distance < pullDistance * 2) { distance = pullDistance + (distance - pullDistance) / 2; } else { distance = pullDistance * 1.5 + (distance - pullDistance * 2) / 4; } } return Math.round(distance); };

从代码可推断出其阻尼策略:

  • 手指位移 ≤pullDistance时,头部跟随手指 1:1 移动
  • 位移在pullDistance2 × pullDistance之间时,超出部分按1/2衰减;
  • 位移超过2 × pullDistance后,再超出部分按1/4衰减,并存在1.5 × pullDistance的封顶参考值。

这一设计保证了即便用户暴力猛拉,头部位移也不会无限增大。

手势与方向判定

  • 触摸方向判定复用useTouch组合式函数(见 use-touch.ts),通过deltaXdeltaY的比较锁定滑动方向:只有垂直下滑isVertical()deltaY >= 0)才会触发下拉逻辑,横向滑动会被忽略,避免与页面横向滚动冲突。
  • touchmove监听通过useEventListener挂载在track元素上,并将passive设为false以消除 Chrome 的被动监听警告,从而允许调用preventDefault阻止页面原生回弹(见 PullRefresh.tsx)。
  • normalloadingsuccess状态或disabled为真时,组件会忽略触摸操作(isTouchable,见 PullRefresh.tsx),加载中重复下拉不会造成状态错乱。

位移与动画

  • 头部位移通过translate3d(0, distance, 0)施加在track元素上,动画时长由animation-duration(默认300ms)控制(见 PullRefresh.tsx)。
  • 头部区域默认通过transform: translateY(-100%)隐藏在内容上方,仅在手指下拉时被「拉出」(见 index.less)。

API 完整参考

Props

参数说明类型默认值
v-model是否处于加载中状态boolean-
pulling-text下拉过程提示文案string下拉即可刷新...
loosing-text释放过程提示文案string释放即可刷新...
loading-text加载过程提示文案string加载中...
success-text刷新成功提示文案string-
success-duration刷新成功提示展示时长(ms)number | string500
animation-duration动画时长number | string300
head-height顶部内容高度number | string50
pull-distance触发下拉刷新的距离number | stringhead-height一致
disabled是否禁用下拉刷新booleanfalse

补充说明(依据 pullRefreshProps):

  • pulling-textloosing-text的默认文案来自国际化语言包vanPullRefresh配置(见 zh-CN.ts),会随组件库语言切换自动变化;loading-text的默认值加载中...则由组件内置兜底。
  • head-heightpull-distance的关系:pull-distance不传时,触发距离与head-height一致。二者均通过makeNumericProp/numericProp声明,支持数字或字符串(如"80")写法,内部以+props.pullDistance转为数字比较。
  • head-height不等于默认的50时,组件会为头部元素显式设置行内height(见 PullRefresh.tsx),测试用例should set height when using head-height对此进行了验证(见 test/index.spec.ts)。

Events

事件名说明回调参数
refresh下拉刷新时触发-
change拖动时或状态改变时触发{ status: string, distance: number }

change事件在手势拖动与状态切换的每个关键节点都会触发,可用于埋点或联动外部 UI。测试用例should emit change event when status changed验证了其载荷结构,例如[{ distance: 20, status: 'pulling' }](见 test/index.spec.ts)。

Slots

名称说明参数
default自定义内容(列表主体)-
normal非下拉状态时顶部内容-
pulling下拉过程中顶部内容{ distance: number }
loosing释放过程中顶部内容{ distance: number }
loading加载过程中顶部内容{ distance: number }
success刷新成功提示内容-

类型定义

组件导出以下类型定义,便于在 TypeScript 项目中获得完整的类型提示:

import type { PullRefreshProps } from 'vant';

相关类型在 PullRefresh.tsx 中通过ExtractPropTypespullRefreshProps推导,并通过 index.ts 对外导出。

主题定制:CSS 变量

组件提供下列 CSS 变量用于自定义样式,使用方式可参考 ConfigProvider 组件。变量在 index.less 的:root/:host中声明,对应的 TypeScript 主题变量类型定义见 types.ts。

名称默认值描述
--van-pull-refresh-head-height50px头部高度
--van-pull-refresh-head-font-sizevar(--van-font-size-md)头部文字字号
--van-pull-refresh-head-text-colorvar(--van-text-color-2)头部文字颜色
--van-pull-refresh-loading-icon-size16px加载图标尺寸

例如在页面级覆盖头部高度与文字颜色:

.demo-page { --van-pull-refresh-head-height: 60px; --van-pull-refresh-head-text-color: #1989fa; }

常见问题

内容未填满屏幕时,只有一部分区域可以下拉?

默认情况下,下拉区域的高度与内容高度保持一致。若希望下拉区域始终为全屏,可以给PullRefresh设置一个与屏幕大小相等的最小高度:

<van-pull-refresh style="min-height: 100vh;" />

PullRefresh 的触发条件是?

触发条件是「父级滚动元素的滚动条在顶部位置」:

  • 如果最近一个可滚动的父级元素是window,则要求window.pageYOffset === 0
  • 如果最近一个可滚动的父级元素是Element,则要求Element.scrollTop === 0

这一点在源码checkPosition中通过getScrollTop(scrollParent.value!) === 0判断,并有对应的单元测试覆盖:mockScrollTop(1)时下拉不会触发update:modelValue,回到顶部后才触发(见 test/index.spec.ts)。

在桌面端无法操作组件?

PullRefresh依赖触摸事件(touchstart/touchmove/touchend),桌面端浏览器默认不支持触屏手势,需配合桌面端适配方案使用,参见 桌面端适配。

下拉距离小于阈值松手会怎样?

未达到pull-distance时松手,组件直接回弹复位(setStatus(0)),不会触发refresh事件,也不会更新v-model。对应测试用例should not emit update:modelValue event after pulling a short distance(见 test/index.spec.ts),这保证了误触下拉不会造成无谓的接口请求。

小结

PullRefresh是 Vant 中一个「小巧但完整」的交互组件:对外提供简洁的v-model+refresh事件协议、五个可自定义的头部状态插槽与完整的主题变量;对内则以五状态机为骨架,配合顶部位置检测、方向锁定、分段阻尼算法,构建了接近原生 App 的刷新手感。阅读其 源码 与 测试用例,也能为自研类似「手势拖拽 + 状态机」组件提供一份高质量的参考范本。

【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询