5.2 KiB
5.2 KiB
title, description
| title | description |
|---|---|
| Popover 气泡卡片 | 浮层基座:12 方位定位、自动翻转、外部点击与 Esc 关闭 |
Popover 气泡卡片
Popover 是所有浮层的基础:Tooltip、Dropdown、Select、DatePicker 都建立在它之上。它负责定位、Teleport、关闭时机与滚动跟随,你只需要提供触发器和内容。
何时使用
基础用法
#trigger 是触发器,#content 是浮层内容;默认 click 触发、显示箭头。
:::demo{name="popover/basic"} :::
方位
placement 支持 12 个方位:top / bottom / left / right 各自带 -start / -end 对齐。空间不足时自动翻转到对面,再不够会沿视口边缘收缩(shift),始终保持 8px 安全边距。
:::demo{name="popover/placement"} :::
触发方式
trigger |
行为 |
|---|---|
click |
点击触发器切换(默认) |
hover |
移入打开(mouseEnterDelay)、移出关闭(mouseLeaveDelay),移入浮层不会关闭 |
focus |
聚焦打开,焦点移出关闭(焦点落在浮层内不关闭) |
contextmenu |
右键时在鼠标位置打开,越界自动回退 |
manual |
组件不接管开关,完全由 open 控制 |
:::demo{name="popover/trigger"} :::
受控用法
传 open 即为受控:所有开关意图都会通过 update:open / openChange 抛出,你可以在里面做权限校验、埋点,或选择不关闭。
:::demo{name="popover/controlled"} :::
箭头与宽度
arrow=false 去掉尖角(菜单类浮层常用);followTriggerWidth 让浮层宽度以触发器宽度为下限(表单控件常用)。
:::demo{name="popover/arrow-width"} :::
API
Props
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
open |
boolean |
— | 受控打开状态;不传即非受控 |
defaultOpen |
boolean |
false |
非受控时的初始状态 |
trigger |
'click' | 'hover' | 'focus' | 'contextmenu' | 'manual' |
'click' |
触发方式 |
placement |
'top' | 'top-start' | … | 'right-end' |
'top' |
期望方位(12 种),越界自动翻转 + 收缩 |
offset |
number |
12 |
与触发器的间距(px) |
arrow |
boolean |
true |
是否显示箭头 |
disabled |
boolean |
false |
禁用:不响应触发(受控 open 仍可强制打开) |
mouseEnterDelay |
number |
100 |
hover 打开延迟(ms) |
mouseLeaveDelay |
number |
100 |
hover 关闭延迟(ms) |
contentClass |
string |
'' |
透传到内容盒的 class,供派生组件定制内容样式 |
followTriggerWidth |
boolean |
false |
浮层宽度以触发器宽度为下限 |
unwrapped |
boolean |
false |
零包裹模式:把事件注入触发器单个根节点,不再包一层 <span> |
popupContainer |
string | HTMLElement | (() => …) |
ConfigProvider 配置 | 浮层挂载容器,默认 body |
Events
| 名称 | 参数 | 说明 |
|---|---|---|
update:open |
(open: boolean) |
打开状态变化(受控与非受控都会触发) |
openChange |
(open: boolean) |
同上,语义别名 |
Slots
| 名称 | 参数 | 说明 |
|---|---|---|
trigger |
— | 触发器内容。unwrapped 时要求单根元素 |
content |
— | 浮层内容 |
类型定义
export type PopoverTrigger = 'click' | 'hover' | 'focus' | 'contextmenu' | 'manual'
export type Placement =
| 'top' | 'top-start' | 'top-end'
| 'bottom' | 'bottom-start' | 'bottom-end'
| 'left' | 'left-start' | 'left-end'
| 'right' | 'right-start' | 'right-end'
export interface PopoverProps {
open?: boolean
defaultOpen?: boolean
trigger?: PopoverTrigger
placement?: Placement
offset?: number
arrow?: boolean
disabled?: boolean
mouseEnterDelay?: number
mouseLeaveDelay?: number
contentClass?: string
followTriggerWidth?: boolean
unwrapped?: boolean
popupContainer?: PopupContainer
}
实现说明
- 定位:纯函数
computePosition计算落点(先按方位摆放 → 主轴溢出则翻转 → 最后 clamp 回视口),浮层用position: fixed+top/left,因此不会受父级overflow: hidden影响(配合 Teleport); - 跟随:打开期间监听捕获阶段的
scroll(覆盖内部滚动容器)、resize,并用ResizeObserver观察触发器与浮层尺寸,变化时重算(用 rAF 合并); - 层级:打开时从 z-index 注册表取一个
popup档的值,关闭时回收(见z-index分层设计),所以弹窗里的下拉也能正确盖在弹窗之上; unwrapped模式的两个坑(已在实现里处理):①#trigger里再嵌<slot />会产出多层 Fragment,需要循环解包到真实元素;② 必须用渲染函数把注入了事件的 vnode 原样渲染,<component :is="vnode">会丢掉事件监听与指令;- 进出场只过渡
opacity/transform,不过渡top/left(否则打开瞬间会从屏幕左上角滑过来)。