不能只靠 uni-popup,因其仅为带遮罩和定位的壳子,缺乏内容渲染、事件透传与状态管理能力,实际开发中需手动处理样式污染、跨端事件不一致、滚动穿透、全局调用及拖拽物理细节等问题。

直接用 uni-popup 就能快速搭出自定义弹窗,但真要控制样式、动画、拖拽或全局调用,必须自己封装组件逻辑——官方组件只提供基础容器,不处理内容渲染、事件透传和状态管理。
为什么不能只靠 uni-popup 组件?
它只是个带遮罩和定位的壳子,实际开发中会立刻遇到这些问题:
-
uni-popup的show是响应式 prop,但父组件每次都要手动维护v-model:show或:show,无法像this.$showModal()那样随时触发 - 内容区域默认没 padding/margin,
slot里写样式容易被父级或平台(尤其是小程序)的默认样式污染 - 安卓 App 端点击遮罩不一定会触发
@maskClick,H5 和小程序对touchmove的拦截策略也不一致,得加@touchmove.stop.prevent - 弹窗打开时页面仍可滚动(尤其在 iOS 微信),需要手动
document.body.style.overflow = 'hidden',关闭时还得恢复
mask + view 手写弹窗最稳的写法
不依赖任何插件,兼容 H5 / 微信小程序 / App 三端,核心就两块:
- 遮罩层用
,固定全屏、z-index: 999、半透明背景,@click="close"关闭 - 弹窗主体用
,position: fixed+top: 50%+transform: translateY(-50%)居中,避免用vh(小程序不支持) - 必须加
@touchmove.stop.prevent到 mask 和 popup 上,否则 iOS 滚动穿透严重 - 打开时设
document.body.style.overflow = 'hidden',关闭时还原;App 端还需调用uni.pageScrollTo({scrollTop: 0})防止页面卡在中间
如何实现全局方法调用(如 this.$showModal())?
本质是 Vue 插件 + 全局事件总线 + 单例弹窗实例。关键点:
- 在
main.js里用app.config.globalProperties.$showModal = function(options) { ... }挂载 - 弹窗组件本身用
ref暴露open()/close()方法,不要用v-model控制显隐 - 通过
uni.$emit('modal:open', options)触发,弹窗组件监听并解析参数(title/content/confirmText 等) - 回调统一走
uni.$on('modal:result', callback),避免父子组件强耦合 - 务必在
onUnload或beforeUnmount中uni.$off,否则多次进入页面会重复监听
拖拽弹窗要注意的物理细节
上下拖拽常见于底部弹出键盘或抽屉式菜单,不是简单绑 @touchstart 就行:
- 必须记录初始
touches[0].clientY和弹窗当前top值,计算位移差,而不是直接改top - 拖拽中用
transform: translateY(...)而非top,保证动画流畅且不触发重排 - 松手后判断位移:超过阈值(比如 100rpx)就
animate到底部全开;否则animate回初始位置,不能用setTimeout硬等 - 微信小程序里
animationAPI 不稳定,建议用requestAnimationFrame+style.transform手动做回弹 - 拖拽过程中要禁用页面滚动,
event.preventDefault()必须在touchmove第一时间调用,晚了 iOS 就会触发 bounce 效果
真正难的不是写出能动的弹窗,而是让同一套代码在 H5 滚动不穿透、小程序不卡顿、App 不闪退——每个平台对 fixed、transform、touch 事件的处理差异,才是自定义弹窗落地时最常掉坑的地方。










