Skip to content

Popup 弹层 ​

通用浮层基础组件。通过 position 统摄 bottom / top / left / right / center / relative / fullscreen 七种形态,内置滚动锁定(useScrollLock)与 useTeleportTarget。

HDialog 与 HBottomSheet 已基于 HPopup(position=center/bottom) 重构,原有 API 完全不变。

基础 · bottom(底部面板) ​

position="bottom" 为底部面板(bottom sheet),支持 拖拽关闭:在面板内(scrollTop=0 时,含手柄区域)向下拖动,面板跟随位移、遮罩渐隐;位移 ≥ 80px 或快速下滑松手即先滑出视口再关闭,短距离松手 250ms 内回弹。内容滚动后(scrollTop > 0)向下拖动只滚动内容、不接管手势。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HButton, HPopup } from 'happier-ui'

const show = ref(false)
</script>

<template>
  <h-button @click="show = true">打开弹层</h-button>

  <!-- 底部面板:支持拖拽关闭(scrollTop=0 时向下拖) -->
  <h-popup v-model="show" position="bottom" title="底部弹层" :handle="true">
    <p>position="bottom"</p>
    <h-button @click="show = false">关闭</h-button>
  </h-popup>

  <!-- 居中对话框 -->
  <h-popup v-model="show" position="center" title="居中弹层">
    <p>position="center"</p>
    <template #footer>
      <h-button @click="show = false">确认</h-button>
    </template>
  </h-popup>

  <!-- 左侧面板 -->
  <h-popup v-model="show" position="left" title="左侧面板">
    <p>position="left"</p>
    <h-button @click="show = false">关闭</h-button>
  </h-popup>
</template>

relative(相对 trigger 定位) ​

无遮罩,JS 计算相对触发元素的固定坐标,支持边缘翻转与 resize/scroll 重算。适用于下拉选择、呼出菜单等。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HButton, HPopup } from 'happier-ui'

const show = ref(false)
const btn = ref(null)
</script>

<template>
  <h-button ref="btn" @click="show = !show">
    {{ show ? '关闭' : '打开 relative 弹层' }}
  </h-button>

  <h-popup
    v-model="show"
    position="relative"
    :trigger-ref="btn"
    title="相对弹层"
    closeable
    radius="sm"
  >
    <p>相对 trigger 定位,带 X 关闭按钮</p>
    <h-button @click="show = false">关闭</h-button>
  </h-popup>
</template>

fullscreen(全屏 + 下滑关闭) ​

position="fullscreen" 让面板完全覆盖视口:无圆角、无 safe-area 内边距,也不会渲染 title / #title header。请在 default slot 内自行组织导航栏或标题。

全屏内容可正常纵向滚动;仅在 scrollTop === 0 且向下拖时接管手势关闭:位移 ≥ 80px 或速度 ≥ 0.3px/ms 即关闭,未达阈值会在 250ms 内回弹。拖动过程中遮罩透明度同步降低,并临时 touch-action: none 锁住面板滚动以免手势与内容滚动打架。handle 在 fullscreen 无效。closeable、Esc 和遮罩点击仍然可用。

  • :swipe-close="false":关闭内置下滑手势。touch 监听不再生效(不 preventDefault),面板 touch-action 从 pan-y 复位为 auto,手势完全交还宿主(宿主用自己的手势调 update:modelValue(false) 关闭)。适用于面板内含多个独立滚动容器、内置手势会与其打架的场景。
  • :keep-alive="true":关闭时仅隐藏(display:none)不卸载 slot 内容,再打开时内容不重建、入场动画照常重放。适合内容初始化昂贵(如 WebGL 背景)的场景。
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HButton, HPopup } from 'happier-ui'

const show = ref(false)
</script>

<template>
  <h-button @click="show = true">打开全屏弹层</h-button>
  <h-popup
    v-model="show"
    position="fullscreen"
    aria-label="全屏设置"
    closeable
    :swipe-close="false"
    :keep-alive="true"
  >
    <div class="fullscreen-page">
      <h2>宿主自管头部</h2>
      <p>内置下滑手势已禁用;关闭由宿主自控。</p>
    </div>
  </h-popup>
</template>

API ​

Props ​

名称类型默认说明
modelValuebooleanfalse受控显隐(v-model)
position'bottom' | 'top' | 'left' | 'right' | 'center' | 'relative' | 'fullscreen''bottom'弹层形态与动画;fullscreen 占满视口并支持下滑关闭
triggerRefHTMLElement | nullnullrelative 定位的触发元素引用(ref 传对象)
closeOnOverlaybooleantrue点击遮罩关闭(relative 无遮罩故无作用)
closeOnEscbooleantrueEsc 键关闭
lockScrollbooleantrue打开时锁定 body 滚动
titlestring—面板标题文本
ariaLabelstring—无障碍标签(title 为空时生效)
teleportstring | HTMLElement | false'body'Teleport 目标(传 false 就地渲染)
closeablebooleanfalse显示 X 关闭按钮(Lucide X 图标)
closeIconPosition'top-left' | 'top-right' | 'bottom-left' | 'bottom-right''top-right'X 按钮位置
radius'none' | 'sm' | 'md' | 'lg'—面板圆角粒度
handlebooleanfalseposition="bottom" 时显示拖拽手柄
keepAlivebooleanfalse关闭时保活 slot 内容(隐藏不卸载,重开重放入场动画);默认关闭即卸载、重开重挂载
swipeClosebooleantruebottom / fullscreen 下滑关闭手势开关;false 时内置手势不生效、面板 touch-action 复位为 auto(其他 position 无作用)
maxWidthstring | number—bottom/top 形态面板最大宽度;number 按 px,string 原样(如 '640px' / 'none' / '100%')。写入 inline CSS 变量 --h-bottom-sheet-max-width,per-instance 覆盖 token;不传默认全宽(edge-to-edge)

Emits ​

名称参数说明
update:modelValuevalue: booleanv-model 双向绑定
close—面板关闭瞬间
open—面板打开瞬间
after-leave—Transition 离场动画结束后
click-overlay—点击遮罩瞬间(先于 close 处理)
click-close-icon—点击 X 关闭按钮瞬间

Slots ​

名称作用域说明
default—面板正文内容
title—自定义标题(覆盖 prop title)
footer—面板底部操作区

行为说明 ​

  • 宽度:bottom/top 面板默认全宽贴底(--h-bottom-sheet-max-width 默认 100%,edge-to-edge)。宽屏需桌面居中卡片感时,传 maxWidth(如 :max-width="640")或全局覆盖 --h-bottom-sheet-max-width 限宽;限宽后面板仍水平居中(margin: 0 auto)。maxWidth 仅影响引用该变量的 bottom/top 形态,left/right/center/relative/fullscreen 不受影响。
  • 滚动锁定:默认 lockScroll: true,打开时通过 useScrollLock(引用计数,模块级安全)禁止 body 滚动,关闭自动还原。
  • 遮罩:除 position="relative" 外均渲染遮罩层。遮罩点击关闭受 closeOnOverlay 控制。
  • Bottom 拖拽手势:bottom 面板(含 handle)在 scrollTop === 0 时向下拖动,面板跟随位移、遮罩透明度同步降低;位移 ≥ 80px 或速度 ≥ 0.3px/ms 松手先平滑滑出视口(250ms)再关闭,未达阈值 250ms 内回弹。内容滚动后(scrollTop > 0)不接管,交还内容滚动;从遮罩区域起拖不触发。swipeClose=false 时手势禁用、面板 touch-action 复位 auto,由宿主全权控制。
  • Fullscreen 手势:内容位于顶部时向下拖动;位移 ≥ 80px 或速度 ≥ 0.3px/ms 关闭,未达阈值则回弹。全屏不渲染内置 header,需由 default slot 自管。swipeClose=false 时内置手势禁用,手势交由宿主处理(面板 touch-action: auto),转场动画、滚动锁、overlay/Esc/closeable 关闭通道不受影响。
  • keepAlive 保活:keepAlive=true 时 slot 内容首渲即挂载、关闭仅隐藏不卸载,重开内容不重建且入场动画重放;隐藏态不响应任何交互(overlay/Esc/X/手势),滚动锁仍随 visible 释放/恢复。
  • Esc:Esc 键触发关闭(closeOnEsc)。
  • 关闭按钮:closeable 显示 X 图标按钮(Heroicons ×,通过 Lucide X);默认隐藏。
  • Title 无障碍:title prop 无 #title slot 时渲染 VS ariaLabelledBy(HLabel 不带 aria-describedby)。
  • Teleport:默认 'body'。传 false 就地渲染。
  • 无 before-close:不做钩子拦截;受控宿主可通过 v-model 提前阻止。

MIT License