n-popup 弹出层
通用弹层容器:遮罩 + 面板 + 可选拖拽头部和关闭按钮,内容经 header / default / footer 三段插槽自由组装,关闭图标可经 closeIcon 插槽替换。五个弹出方向(上 / 下 / 左 / 右 / 居中);bottom 方向支持档位式面板(snap-points):面板真实高度随当前档位变化,拖拽头部(把手条 + header 插槽内容)在各档间吸附切换、拖过最低档可关闭——地图应用底部详情面板的常见形态。
- 受控显隐:
visible置 true 弹出;点内置关闭按钮、点遮罩(mask-closable开启时)或把手拖拽超阈值时派发close(来源)("mask" / "drag" / "close")并回传update:visible(false)——组件不自行收起,在回调里把visible改回 false 即完成闭环(关闭路径带退场过渡,滑出后自动卸载)- 档位值是面板高度(px,建议升序传入),
-1表示全屏档(自动换算窗口高度);空数组(默认)= 单段弹层,高度由内容自适应或size指定- 当前档受控于
snap-index:拖拽吸附与外部改档均经update:snap-index/change同步,外部改档时面板带过渡动画切档- 出入场采用 transform 过渡,档位吸附采用 height 过渡(时长由
duration控制);拖动即时跟手,外部 snapPoints 容量更新同样使用 duration,打开态 transform 始终为零- 拖拽热区是面板头部整块(把手条 +
header插槽);向上拖到最高档顶到视口顶即止(去上圆角),向下拖过最低档走阻尼行程、超过drag-close-distance判关- 键盘 / 选择器族弹层(车牌键盘、日期滚轮等)自带强定制面板、结构内置自包含;n-popup 面向「内容即弹层」的通用场景(表单、筛选、地图详情),不含任何业务结构
平台兼容性
| App | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ小程序 |
|---|---|---|---|---|---|---|
| √ | √ | √ | × | × | × | × |
API
属性
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|
| visible | 显隐开关(受控模式:点遮罩/拖拽/内置按钮关闭只派发 close 并回传 update:visible(false),收起由使用者改回 false) | Boolean | false | - |
| direction | 弹出方向:面板钉在视口对应边,center 居中;档位(snap-points)仅 bottom 支持 | String | "bottom" | top / bottom / left / right / center |
| snap-points | 档位高度数组(px,仅 bottom 生效,建议升序):面板真实高度随当前档位变化,拖拽把手在档位间吸附;-1 = 全屏档(自动换算窗口高度);空数组 = 单段弹层 | Array<number> | [] | - |
| snap-index | 当前档位下标(0 起,指向 snap-points 数组;拖拽吸附或外部改档后经 update:snap-index / change 同步;越界自动夹回界内) | Number | 0 | - |
| size | 面板尺寸:bottom/top = 高度、left/right/center = 宽度(如 "300px");空 = bottom/top 高度由内容自适应、侧向与居中宽 75%;snap-points 非空时忽略 | String | "" | - |
| show-handle | 顶部拖拽把手小条(仅 bottom 方向渲染):与 header 插槽共同构成拖拽热区,档位间拖拽吸附、单段模式下拉关闭 | Boolean | true | - |
| drag-close | 头部拖拽关闭:档位模式拖过最低档、单段模式下拉超过 drag-close-distance 时派发 close | Boolean | true | - |
| drag-close-distance | 拖拽关闭判定阈值(px,阻尼后的可见位移超过该值才判关;调小更灵敏) | Number | 60 | - |
| duration | 出入场 / 档位吸附 / 遮罩淡入淡出的过渡时长(毫秒):0 = 即显模式(无过渡、瞬开瞬关,键盘 / 滚轮类输入工具推荐);退场卸载延时自动按此时长联动 | Number | 300 | - |
| show-mask | 渲染半透明遮罩(关闭后弹层区域透出页面内容;关闭仅剩面板本体,遮罩点击仍可关闭) | Boolean | true | - |
| mask-closable | 点击遮罩(含 show-mask 关闭时的透明区域)派发 close 并回传 update:visible(false) | Boolean | true | - |
| safe-area-inset-bottom | bottom 面板底部安全区垫层(iPhone 小白条 / Android 手势条高度,自动读取) | Boolean | true | - |
| safe-area-inset-top | top 面板顶部安全区垫层(沉浸式状态栏高度,自动读取;带原生导航栏的页面视口本就从导航栏下沿起算,勿开启) | Boolean | false | - |
| show-close | 显示内置关闭按钮,默认隐藏,已有自定义操作区时不会重复显示 | Boolean | false | - |
| close-position | 按钮停靠面板内的位置;内容需为该角落预留空间 | String | "top-right" | top-left / top-right / bottom-left / bottom-right |
| close-icon-size | 默认图标尺寸(px),自定义 closeIcon 插槽自行控制 | Number | 18 | - |
| close-icon-color | 图标颜色;空值跟随主题次要文字色,插槽可继承 | String | "" | 与 css 相同 |
| close-style | 关闭按钮容器样式,可定制位置、触控盒尺寸、背景和圆角 | String | "" | 与 css 相同 |
| panel-style | 面板外观及内边距;档位模式仅接收已定义外观属性,尺寸、定位及动画由组件管理 | String | "" | 普通模式与 css 相同;档位模式允许的属性见上文 |
| handle-style | 微调把手小条的行内样式,如 "background-color:#999999;width:48px" | String | "" | 与 css 相同 |
| custom-class | 挂到根节点的额外 class(根节点是全屏透明层,无默认视觉;调 z-index 等场景使用) | String | "" | - |
| custom-style | 根节点行内样式 | String | "" | 与 css 相同 |
事件
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| open | 本次面板挂载并开始入场;高度与档位变化不触发 | - |
| opened | 本次入场过渡完成;提前关闭或卸载不触发 | - |
| closed | 本次退场完成并卸载;退场中重新打开不触发 | - |
| close | 请求关闭时触发,参数为来源:"mask" = 点遮罩(mask-closable 开启时)、"drag" = 头部拖拽超过关闭阈值、"close" = 内置关闭按钮;组件不自行收起,同帧回传 update:visible(false);入场未就绪 / 退场进行中不派发 | String(来源) |
| update:visible | 关闭建议(false):配合 :visible 手动双向控制显隐 | Boolean |
| update:snap-index | 档位吸附完成后的新档下标;配合 :snap-index 手动双向(拖拽与外部改档均经此同步) | Number |
| change | 档位切换完成时触发(与 update:snap-index 同参同发) | Number(吸附后的档位下标) |
| move | bottom 档位面板进入打开态及高度、拖动状态或时长变化时通知;退场与隐藏时不触发 | NPopupMoveDetail: |
插槽
| 插槽名 | 说明 |
|---|---|
| default | 面板主体内容(面板为纵向 flex 容器):面板定高或档位模式(侧边 / size / snap-points)时长内容放 scroll-view 给 flex: 1,自动滚且 footer 贴底;未设 size 的 top/bottom 与 center 面板高度随内容,长列表请给定高(flex: 1 在内容自适应高的面板里不生效) |
| header | 顶部文案 / 标题区:全方向可用——bottom 方向时位于拖拽热区内(与把手条整块可拖),其余方向渲染在面板顶部(top 方向开 safe-area-inset-top 时在状态栏垫块之下) |
| closeIcon | 内置关闭按钮的图标内容,覆盖默认 n-icon;按钮容器、关闭事件和手势拦截仍由组件负责,仅 show-close=true 时显示 |
| footer | 底部操作区(确定 / 取消按钮等):全方向可用,渲染在面板底部、安全区垫块之上(小白条避让由 safe-area-inset-bottom 承担) |
实现说明
| 机制 | 参考做法 | 本组件做法与理由 |
|---|---|---|
| 显隐生命周期 | 弹层区分 open/opened/close/closed | open 为入场开始,opened 为入场完成,closed 为退场完成;close 保留既有受控关闭请求语义;开关与卸载统一清理计时器 |
| 档位布局 | 普通弹层以固定 size 布局,拖动使用 transform | 档位模式改变真实高度,让 header/default/footer 与正文随容器自然伸缩;单纯平移最大高度面板会把低档的底部操作区移到窗口外。这是本库对可变高面板的布局选择,需要承担重排成本 |
| 档位与外部容量更新 | 浮动面板有 move/change;加密源未核实动画实现 | 手势吸附使用高度过渡;外部 snapPoints 容量更新由duration统一控制,不移动聚焦输入框祖先 |
| 面板变化通知 | 浮动面板元数据列出 move,未提供可核实的载荷定义 | 保留已确认的 NPopupMoveDetail 高度、拖动状态与时长契约,供外部内容联动;不声称与参考载荷等价。事件只观察内部状态,不参与组件动画决策 |
| 高度与样式权属 | 弹层和可拖动容器从尺寸属性、手势状态统一计算渲染尺寸 | 档位模式中,几何状态同时供给面板高度与 move;外观通道仅接收文档列出的装饰、内边距、文字和主题变量,定位、尺寸及动画不由外部声明覆盖,固定 border-box。无档位时不改变既有外观定制契约 |
| 过渡与样式权属 | 可拖动容器根据手势状态统一切换过渡属性与时长 | 档位模式排除外部位移与过渡覆盖,统一管理 transform、height 和四侧 padding 的过渡;内边距参与过渡是通用布局能力,支持外部容量变化时内容同步伸缩,不包含键盘或页面业务判断 |
| 拖动区域 | 头部与主体分离 | header 与把手共用已有拖动区,正文输入保持原生交互 |
| 内置标题 | 部分弹层提供 title / titleStyle 文字标题属性 | 不设标题属性:header 插槽承载任意标题区(文字、图标行、副标题),标题只是插槽内容的一种,避免与插槽双轨 |
| 内置关闭按钮 | 图标 / 颜色 / 尺寸 / 位置属性组合,部分仅图标名 | showClose 开关 + closePosition 四角 + closeIconSize / closeIconColor / closeStyle 微调 + closeIcon 插槽整颗替换,属性面取并集 |
| 遮罩定制 | 遮罩背景色 / 透明度走独立属性 | 遮罩色走 --n-popup-mask-color 主题令牌(运行时变量或 uni.scss 覆盖),深层定制走 custom-style;不另设透明度属性,令牌即唯一事实源 |
| 安全区 | 底部垫层属性;个别弹层另有顶部垫层与导航栏高度补偿属性 | bottom / top 双方向对称开关(safe-area-inset-bottom 默认开、safe-area-inset-top 默认关——带原生导航栏的页面开启会多垫一截);导航栏高度补偿不收:本库 fixed 弹层即视口层,导航栏占位由页面结构决定 |
| 关闭拦截 | 个别弹层提供 beforeOpen / beforeClose 拦截事件 | 受控模型天然可拦截:close 回调里不把 visible 改回 false 即拒绝关闭(异步校验后再收起),无需独立拦截事件 |
| 居中缩放 | 居中弹窗可选缩放开关(默认值无公开事实源) | center 出入场恒为缩放 + 淡入(scale 0.96→1),不设开关:已核实的同类实现均为恒缩放,开关默认值无据可依 |
| 内容区拖动 | 弹层提供「内容可拖 / 把手可拖」双开关(默认值无公开事实源) | 拖拽热区固定为面板头部整块,内容区不接管手势(滚动与拖拽互不干扰);让内容可拖属嵌套滚动协调,本库不做 |
| 关闭后常驻 | 个别弹层提供内容缓存开关(关闭后 DOM 常驻、仅视觉隐藏) | 关闭即卸载(关闭态零节点零拦截,与库内遮罩 / 弹层族契约一致);需要保留内部状态由使用者在关闭回调里自存,App 端常驻面板的插拔重绘问题也由此从结构上排除 |
| 传送门 | 个别弹层提供 portal 传送门属性(H5 / MP 端把弹层搬到根节点) | 不设传送门:Web 端弹层打开时锁定页面滚动并隔离背景焦点(焦点栈管理),层级固定即视口层 |
- 真实档位布局:底部面板始终对齐视口底边,height 等于当前可视档高;打开态不保留向下位移。默认插槽的 flex:1 由真实面板剩余空间决定,header、footer 自然占位。用户拖动与松手吸附调整高度,外部容量更新与档位吸附共用duration,设0即时重排,避免键盘避让与组件过渡叠加。
- 拖拽越界行为:向上拉过最高档钳死在视口顶(面板顶贴顶即止,底部不脱出视口);向下拉过最低档位移打 4 折(阻尼回弹手感),超过
drag-close-distance判定关闭,面板冻结在松手位置等退场动画接管 - 手势分区:拖拽热区 = 面板头部整块(把手条 + header 插槽内容);面板内容区(含 scroll-view)不接管手势,滚动与拖拽互不干扰。内容滚到顶再下拉接管面板属嵌套滚动手势,uni-app x 无可靠支持,不建议依赖。
drag-close=false且无档位时头部换装静态变体:不挂触摸拦截,header 插槽内的自定义控件与滚动照常工作(两侧变体经 key 整根重建切换) - 打断与卸载安全:触摸被打断(touchcancel)不算成功拖拽,面板回弹到原位不判关;组件卸载时清掉出入场计时器,退场中的实例被父组件移除不会产生残留回调
- 全屏档视觉:顶边贴视口顶时自动去上圆角(拖拽中跟手切换);重开弹层即刷新
- 全屏档高度在档位求值时读取(
uni.getWindowInfo().windowHeight,打开与外部改 snap-points 都会重新求值),打开后才补入的全屏档同样拿到正确高度;窗口旋转 / 分屏后改档或重开弹层即刷新。全屏档 = 弹层视口整高(有原生导航栏时从导航栏下沿起算),top 方向的状态栏避让由safe-area-inset-top承担
定制
主题令牌(--n-popup-* 运行时 CSS 变量,或 uni.scss 覆盖 $n-popup-*):遮罩色(mask-color)、面板圆角(radius)、把手宽 / 高 / 颜色(handle-width / handle-height / handle-color,把手色随深浅主题自动切换)。过渡时长走 duration prop(毫秒,运行时控制,0 = 即显)。面板个别微调走 panel-style,把手小条走 handle-style,关闭按钮走 close-style,根节点(全屏透明层)走 custom-class / custom-style。
档位性能与滚动
档位模式拖动会改变真实高度并触发布局;它不属于合成层平移优化。内容应以 flex:1;min-height:0 承接剩余空间,长列表放独立 scroll-view,不在触摸回调中查询节点尺寸。普通非档位弹层仍以 transform 拖动。测试只能证明状态与几何关系,不能替代目标设备上的帧率、输入框内部滚动和键盘动画验收。