---
title: Popover 气泡卡片
description: 浮层基座:12 方位定位、自动翻转、外部点击与 Esc 关闭
---
# Popover 气泡卡片
`Popover` 是**所有浮层的基础**:Tooltip、Dropdown、Select、DatePicker 都建立在它之上。它负责定位、Teleport、关闭时机与滚动跟随,你只需要提供触发器和内容。
## 何时使用
- 需要在触发器旁展示一段内容(说明、小表单、卡片)时;
- 需要容器级别的自定义浮层时。更具体的场景请直接用上层封装:[Tooltip](/ui/overlay/tooltip)、[Dropdown](/ui/overlay/dropdown)、[Select](/ui/form/select)。
## 基础用法
`#trigger` 是触发器,`#content` 是浮层内容;默认 click 触发、显示箭头。
## 方位
`placement` 支持 12 个方位:`top` / `bottom` / `left` / `right` 各自带 `-start` / `-end` 对齐。空间不足时**自动翻转到对面**,再不够会沿视口边缘收缩(shift),始终保持 8px 安全边距。
## 触发方式
| `trigger` | 行为 |
| --- | --- |
| `click` | 点击触发器切换(默认) |
| `hover` | 移入打开(`mouseEnterDelay`)、移出关闭(`mouseLeaveDelay`),移入浮层不会关闭 |
| `focus` | 聚焦打开,焦点移出关闭(焦点落在浮层内不关闭) |
| `contextmenu` | 右键时在**鼠标位置**打开,越界自动回退 |
| `manual` | 组件不接管开关,完全由 `open` 控制 |
## 受控用法
传 `open` 即为受控:所有开关意图都会通过 `update:open` / `openChange` 抛出,你可以在里面做权限校验、埋点,或选择不关闭。
## 箭头与宽度
`arrow=false` 去掉尖角(菜单类浮层常用);`followTriggerWidth` 让浮层宽度以触发器宽度为下限(表单控件常用)。
## 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` | 零包裹模式:把事件注入触发器单个根节点,不再包一层 `` |
| `popupContainer` | `string \| HTMLElement \| (() => …)` | ConfigProvider 配置 | 浮层挂载容器,默认 `body` |
### Events
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| `update:open` | `(open: boolean)` | 打开状态变化(受控与非受控都会触发) |
| `openChange` | `(open: boolean)` | 同上,语义别名 |
### Slots
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| `trigger` | — | 触发器内容。`unwrapped` 时**要求单根元素** |
| `content` | — | 浮层内容 |
### 类型定义
```ts
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` 里再嵌 `` 会产出多层 Fragment,需要循环解包到真实元素;② 必须用渲染函数把注入了事件的 vnode 原样渲染,`` 会丢掉事件监听与指令;
- 进出场只过渡 `opacity` / `transform`,**不**过渡 `top/left`(否则打开瞬间会从屏幕左上角滑过来)。