Vant Weapp Popup 弹出层组件完全指南:属性、事件、动画与滚动穿透解决方案
【免费下载链接】vant-weapp轻量、可靠的小程序 UI 组件库项目地址: https://gitcode.com/gh_mirrors/va/vant-weapp
Popup 弹出层是 Vant Weapp 小程序组件库中最基础也最常用的容器组件之一,用于承载弹窗、底部操作栏、信息提示、选择面板等各类浮层内容,并原生支持多个弹出层叠加展示。本文以 packages/popup/README.md 为骨架,结合组件源码(packages/popup/index.ts、packages/popup/index.wxml、packages/popup/popup.wxml、packages/popup/index.less)与配套测试,系统讲解它的引入方式、全部 Props/Events、四种弹出位置、关闭图标、圆角弹窗、安全区适配、动画原理以及滚动穿透的完整解决方案,读完即可在真实小程序项目中熟练落地使用。
组件介绍
Popup 弹出层本质是一个固定定位(position: fixed)的浮层容器,可用来展示任意自定义内容。它具备三个核心特性:
- 可控显示:通过
show属性(布尔值)控制弹出与收起; - 可叠加:同一页面可同时存在多个 Popup 实例,配合
z-index实现层级管理; - 动画内置:进入/离开均带有过渡动画,且动画时长可配置。
从 packages/popup/index.ts 的实现看,Popup 组件通过VantComponent注册,并混入了transition(false)过渡动画 Behavior(packages/mixins/transition.ts),因此它的显示隐藏、动画生命周期与 Transition 组件保持同一套机制(详见下文"动画与事件"一节)。
引入组件
在app.json(全局)或页面级index.json中声明组件,即可在对应作用域使用:
"usingComponents": { "van-popup": "@vant/weapp/popup/index" }引入后在 WXML 中直接书写<van-popup>标签。完整的快速上手流程可参考 docs/markdown/quickstart.md 中的组件引入章节。组件本身依赖van-overlay(遮罩层)、van-icon(关闭图标)与van-transition等内部组件,引入 Popup 时无需额外手动注册这些依赖,它们随组件包一并可用。
代码演示
基础用法
通过show属性控制弹出层是否展示,bind:close在弹出层关闭时触发,用于同步页面数据:
<van-cell title="展示弹出层" is-link bind:click="showPopup" /> <van-popup show="{{ show }}" bind:close="onClose">内容</van-popup>Page({ data: { show: false, }, showPopup() { this.setData({ show: true }); }, onClose() { this.setData({ show: false }); }, });要点:
show从false变为true时组件执行进入动画,从true变为false时执行离开动画,动画结束后隐藏节点;- 点击遮罩层默认会触发
close事件(可通过close-on-click-overlay关闭此行为),因此需要在事件回调中把show同步回false,形成完整的"关闭-回写"闭环; - 组件根节点默认不带内边距,可在标签内部直接书写内容,或通过
custom-style传入内边距等样式(示例工程中即使用了custom-style="padding: 30px 50px",见 packages/popup/demo/index.wxml)。
弹出位置
通过position属性设置弹出位置,默认居中弹出,可取值center、top、bottom、left、right:
<van-popup show="{{ show }}" position="top" custom-style="height: 20%;" bind:close="onClose" />位置样式在 packages/popup/index.less 中定义:
center:top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0)居中定位;top/bottom:占满整行宽度(width: 100%),分别贴顶部、贴底部;left/right:垂直居中,贴左、贴右,高度通常需配合custom-style指定。
注意:弹出层宽度/高度默认由内容撑开(居中弹窗)或整行/整列(top/bottom 为width: 100%)。像height: 20%、width: 20%; height: 100%这类尺寸需要配合custom-style显式指定,示例工程中四个方向的完整写法参见 packages/popup/demo/index.wxml。
由于position同时驱动过渡动画的方向类名(van-top-*、van-bottom-*等),index.ts中为position注册了observeClass观察器,动态修改位置时会同步更新动画 class(packages/popup/index.ts)。
关闭图标
设置closeable属性后,弹出层右上角会显示关闭图标;close-icon可自定义图标名称或图片链接(默认cross);close-icon-position可调整图标位置:
<van-popup show="{{ show }}" closeable position="bottom" custom-style="height: 20%" bind:close="onClose" /> <!-- 自定义图标 --> <van-popup show="{{ show }}" closeable close-icon="close" position="bottom" custom-style="height: 20%" bind:close="onClose" /> <!-- 图标位置 --> <van-popup show="{{ show }}" closeable close-icon-position="top-left" position="bottom" custom-style="height: 20%" bind:close="onClose" />实现细节:
- 关闭图标是组件内部渲染的
<van-icon>,name="{{ closeIcon }}"支持内置图标名或自定义图片链接(packages/popup/popup.wxml); - 图标位置通过修饰类
van-popup__close-icon--top-left / top-right / bottom-left / bottom-right控制,四角间距统一使用 CSS 变量--popup-close-icon-margin(默认 16px,见 packages/popup/index.less); - 点击图标触发
onClickCloseIcon,直接$emit('close')通知页面关闭(packages/popup/index.ts); - 图标的颜色、字号、层级均可通过
--popup-close-icon-color、--popup-close-icon-size、--popup-close-icon-z-index等 CSS 变量覆盖(默认值见 packages/common/style/var.less)。
圆角弹窗
设置round属性后,弹窗会根据弹出位置自动添加对应方向的圆角:
<van-popup show="{{ show }}" round position="bottom" custom-style="height: 20%" bind:close="onClose" />圆角逻辑位于 packages/popup/index.less:
center:四角统一圆角;top:仅底部两角圆角;bottom:仅顶部两角圆角(最常见的"底部圆角弹窗"形态);left/right:对应侧的两角圆角。
圆角半径使用 CSS 变量--popup-round-border-radius控制,默认 16px(packages/common/style/var.less)。
禁止滚动穿透
使用组件时会发现:当弹窗内容滚动到底部后继续划动,会连带滚动底层页面,这就是滚动穿透。
组件提供lock-scroll属性(默认true)处理部分滚动穿透问题。其实现位于遮罩层:当lock-scroll为真时,遮罩节点绑定catch:touchmove阻止触摸事件冒泡(packages/overlay/overlay.wxml),从而阻止遮罩层区域内的滚动穿透。
但受小程序平台自身限制,弹窗内容区域仍可能出现滚动穿透。官方推荐一个更彻底的方案——使用page-meta组件动态修改页面样式:
<!-- page-meta 只能是页面内的第一个节点 --> <page-meta page-style="{{ show ? 'overflow: hidden;' : '' }}" /> <van-popup show="{{ show }}" catch:touchstart />方案说明:
- 当小程序基础库最低版本在2.9.0 以上时,即可使用 page-meta 组件;
page-meta必须是页面内的第一个节点;- 弹窗打开时通过
page-style给页面根节点加overflow: hidden,从根上禁用页面滚动;关闭后恢复为空字符串; - 配合
<van-popup show="{{ show }}" catch:touchstart />捕获弹出层上的触摸起始事件,双重保险。
API
Props
完整参数表如下,其中标注版本号的参数(v1.7.3、v1.10.14)为后续版本新增能力:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| show | 是否显示弹出层 | boolean | false |
| z-index | z-index 层级 | number | 100 |
| overlay | 是否显示遮罩层 | boolean | true |
| position | 弹出位置,可选值为topbottomrightleft | string | center |
| duration | 动画时长,单位为毫秒 | number | object | 300 |
| round | 是否显示圆角 | boolean | false |
| custom-style | 自定义弹出层样式 | string | '' |
| overlay-style | 自定义遮罩层样式 | string | '' |
| close-on-click-overlay | 是否在点击遮罩层后关闭 | boolean | true |
| closeable | 是否显示关闭图标 | boolean | false |
| close-icon | 关闭图标名称或图片链接 | string | cross |
| close-icon-position | 关闭图标位置,可选值为top-leftbottom-leftbottom-right | string | top-right |
| safe-area-inset-bottom | 是否为 iPhoneX 留出底部安全距离 | boolean | true |
| safe-area-inset-top | 是否留出顶部安全距离(状态栏高度) | boolean | false |
| safe-area-tab-bar | 是否留出底部 tabbar 安全距离(在使用 tabbar 组件 & 小程序自定义 tabbar 时,popup 组件层级无法盖住 tabbar) | boolean | false |
lock-scrollv1.7.3 | 是否锁定背景滚动 | boolean | true |
root-portalv1.10.14 | 是否从页面中脱离出来,用于解决各种 fixed 失效问题,微信基础库 >=2.25.2 | boolean | false |
各参数的源码落点(packages/popup/index.ts)与行为细节:
- show / duration / name:这三个属性来自混入的
transitionBehavior(packages/mixins/transition.ts),其中duration类型为null(即不限制),既可以是数字(如300),也可以是对象{ enter, leave }分别指定进入/离开时长; - position / transition(非文档公开属性):组件额外支持
transition属性覆盖动画名称。observeClass观察器逻辑为:name = transition || position,即默认以position作为动画名;若transition传入'none',则动画时长会被临时置 0(原时长保存在originDuration,恢复时还原),实现"无动画"效果(packages/popup/index.ts); - z-index:默认
100,同时透传给内部遮罩层与弹出层节点(packages/popup/index.wxs),多弹窗叠加时通过增大该值控制谁在上层; - safe-area-tab-bar:开启后,底部弹出的 Popup 会把
bottom抬高到 tabbar 高度之上(CSS 变量--tabbar-height,默认 50px,见 packages/popup/index.less 与 packages/common/style/var.less),用于解决自定义 tabbar 场景下弹层被盖住的问题; - safe-area-inset-bottom / safe-area-inset-top:分别通过
env(safe-area-inset-bottom)、env(safe-area-inset-top)计算安全区(packages/popup/index.less),前者默认开启,适用于底部弹出的操作面板; - root-portal:开启后,弹层内容会渲染进
<root-portal>节点脱离页面 DOM,规避各种fixed失效问题(如被transform祖先节点影响),需要微信基础库 >= 2.25.2。模板中通过wx:if="{{ rootPortal }}"在"根节点渲染"与"普通渲染"两条分支间切换(packages/popup/index.wxml)。
Events
| 事件名 | 说明 | 参数 |
|---|---|---|
| bind:close | 关闭弹出层时触发 | - |
| bind:click-overlay | 点击遮罩层时触发 | - |
| bind:before-enter | 进入前触发 | - |
| bind:enter | 进入中触发 | - |
| bind:after-enter | 进入后触发 | - |
| bind:before-leave | 离开前触发 | - |
| bind:leave | 离开中触发 | - |
| bind:after-leave | 离开后触发 | - |
事件触发链路(源码可查):
- click-overlay / close:点击遮罩层时
onClickOverlay先$emit('click-overlay'),若closeOnClickOverlay为真再$emit('close')(packages/popup/index.ts)。遮罩的点击事件由内部van-overlay的bind:click转发(packages/popup/index.wxml); - close:除点击遮罩层外,点击关闭图标(
onClickCloseIcon)同样触发close(packages/popup/index.ts); - before-enter / enter / after-enter / before-leave / leave / after-leave:全部由
transitionBehavior 在动画各阶段$emit发出(packages/mixins/transition.ts)。其中进入流程为"before-enter → enter → after-enter",离开流程为"before-leave → leave → after-leave";动画结束后若show仍为假,组件将display置为false隐藏节点。
外部样式类
| 类名 | 说明 |
|---|---|
| custom-class | 根节点样式类 |
custom-class会附加到弹出层根节点(packages/popup/popup.wxml)。此外,从组件注册的classes列表(packages/popup/index.ts)可以看到,它内部还支持enter-class、enter-active-class、enter-to-class、leave-class、leave-active-class、leave-to-class以及close-icon-class这 7 个过渡动画阶段类名与关闭图标类名,可在使用 Transition 组件时按需传入,实现更细粒度的动画与图标自定义。
动画机制与叠加弹窗
Popup 的进入/离开动画由transitionBehavior(packages/mixins/transition.ts)统一驱动:
show变为true时执行enureEnter:通过两帧requestAnimationFrame依次设置enter与enter-to过渡类名,配合 CSStransition完成动画;show变为false时执行enureLeave:等待进入动画完成后,再执行离开动画,动画时长结束后触发onTransitionEnd并隐藏节点;- 动画时长取自
duration(数字或{ enter, leave }对象),过渡类名由name(默认取position)生成,如van-bottom-enter、van-bottom-leave-to(CSS 定义见 packages/popup/index.less)。
由于position直接映射动画方向(top/bottom上下滑入、left/right左右滑入、center淡入、scale缩放淡入),因此同一个 Popup 只需切换position,出入动画会自动跟随方向变化。
关于"支持多个弹出层叠加展示":每个<van-popup>都是独立实例、独立管理自身show与动画状态,且默认z-index: 100可逐层调大。实际项目中常见的"底部操作面板 + 二次确认弹窗"叠加场景,即为两个 Popup 实例同时存在、各自受控。
测试与示例工程
仓库为 Popup 提供了完整的演示与测试支撑:
- 示例页面:packages/popup/demo/index.wxml 覆盖基础用法、四种弹出位置、关闭图标(默认/自定义/位置)、圆角弹窗共 9 个场景,配套逻辑见 packages/popup/demo/index.ts(各场景通过
toggle(type, show)统一管理show状态); - 快照测试:packages/popup/test/demo.spec.ts 使用
miniprogram-simulate加载 demo 并断言渲染结果与快照一致(快照见 packages/popup/test/snapshots/demo.spec.ts.snap),可在修改组件后运行测试验证渲染不回归; - 已编译产物:小程序可直接使用的构建结果位于 lib/popup/index.js、lib/popup/index.wxml、lib/popup/index.wxss 等文件,
@vant/weapp/popup/index即指向该产物目录。
样式定制
除了custom-style与overlay-style两个实例级样式入口,Popup 的视觉细节几乎全部可以通过 CSS 变量覆盖,适合在全局或页面级主题中统一调整:
| CSS 变量 | 默认值 | 作用 |
|---|---|---|
--popup-background-color | @white(#fff) | 弹出层背景色 |
--popup-round-border-radius | 16px | 圆角弹窗的圆角半径 |
--popup-close-icon-size | 18px | 关闭图标字号 |
--popup-close-icon-color | @gray-6(#969799) | 关闭图标颜色 |
--popup-close-icon-margin | 16px | 关闭图标与边缘的间距 |
--popup-close-icon-z-index | 1 | 关闭图标层级 |
--tabbar-height | 50px | safe-area-tab-bar场景下弹层抬升高度 |
以上默认值均可在 packages/common/style/var.less 与 packages/popup/index.less 中核对。组件内样式统一采用var(--xxx, 默认值)的兜底写法,未定义变量时自动回退到默认值,因此按需覆盖即可,不会影响其他组件。
常见问题
- 点击遮罩层不关闭?检查是否误设
close-on-click-overlay="{{ false }}";同时注意close事件只负责通知,仍需在页面回调中把show置回false。 - 弹层被自定义 tabbar 盖住?使用底部 tabbar 组件且为自定义 tabbar 时,设置
safe-area-tab-bar为true,弹层会自动抬升到--tabbar-height(默认 50px)之上。 - fixed 定位失效(弹出层显示位置错乱)?当页面或祖先节点存在
transform、filter等属性导致fixed失效时,开启root-portal(微信基础库 >= 2.25.2)让弹层脱离页面渲染。 - 弹窗内容区域滚动穿透无法解决?
lock-scroll只能拦截遮罩层区域的滚动穿透;内容区域请采用官方推荐的page-meta(基础库 >= 2.9.0)+catch:touchstart组合方案。 - 需要无动画弹出?通过内部
transition属性传入'none',组件会临时将动画时长置 0,实现瞬时显示/隐藏。
【免费下载链接】vant-weapp轻量、可靠的小程序 UI 组件库项目地址: https://gitcode.com/gh_mirrors/va/vant-weapp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考