PVQ-UI
v1.4.3
Gitee

文档

PVQ-UI 的开发方案、项目规则、组件清单与调用方法。文档为代码唯一真源,本页直接渲染仓库中的真实 Markdown。

Popover 回流落库

PVQ-UI 回流落库:锚点浮层 .pvq-popover(v1.3.1)

状态:已落库(库版本 1.3.1,base 层,experimental) 来源:pvq-weixinyuedu 插件提出的《回流需求-pvq-ui-浮层组件.md》(依据《PVQ-UI项目规则》§2「通用组件只存在于 pvq-ui;发现缺失 → 回流到库,先文档、后代码」) 关联:《PVQ-UI组件库开发方案.md》§4.1/§7、《组件提取对照表.md》§2.16、《设计令牌与组件清单.md》Popover 条目、《PVQ-UI组件库调用手册.md》§4.7 机器可读真源:site/inc/components.json 的 popover 条目


1. 为什么收进库

插件「引文同步」要把微信读书点评以悬浮卡片呈现在引语旁(桌面悬停、触屏点按、键盘聚焦)。盘点库中 34 个组件后确认没有任何浮层气泡类:

既有组件 为什么不能替代
card 常驻内容容器,padding:24px、overflow:hidden、带入场动画,无定位与开合能力
collapse 原地展开,撑高文档流 ⇒ 布局位移;需求要的是「出卡片」而非「展开」
confirm-modal 模态、阻断交互、居中,不适合「扫一眼就关」的轻量注释
toast 固定角落通知,不锚定触发元素

缺的能力是以某个元素为锚、脱离文档流、按需显隐的浮层——这是前端通用原语(术语解释、作者卡、预览、迷你详情都会用到),留在各插件各写一遍正是《项目规则》§2 要避免的漂移。故收进库。


2. 落库决策(本案的四个关键取舍)

  1. 层级:base 层(assets/css/pvq-ui.css)。前后台都要用,且不依赖 admin / frontend 层。
  2. 稳定性:experimental。按《库演进治理方案》§5.3,新组件经 1~2 个插件验证后再转 stable(weixinyuedu 为第一个验证方)。
  3. 开关契约只有两个状态:hidden 属性 = 彻底不显示(display:none);--open 修饰符 = 淡入显示。库只认这两个,不碰业务。
  4. 默认即浮层态,不做 pvq-wr-js 式的渐进增强分支(与插件侧现状的差异,见 §7)。
    • 库 CSS 里 .pvq-popover 本身就是 position:fixed + opacity:0; visibility:hidden,不需要调用方在 <head> 同步加类来消除 FOUC——这比插件侧「head 脚本加 .pvq-wr-js」更省事,插件收敛后 thought-boot.js 可删。
    • 代价:JS 缺席时浮层保持不可见(信息需服务端另给兜底,如 <noscript> 中的静态呈现)。这个取舍是刻意的:库不在无坐标的情况下伪装显示浮层,否则 position:fixed 且 top/left:auto 的元素会停在视口左上角,比不可见更糟。

3. 类名契约(BEM 双横线,遵守 §6)

<button type="button" tabindex="0"
        data-pvq-popover="pvq-demo-pop" aria-describedby="pvq-demo-pop">悬停 / 点按我</button>

<div class="pvq-popover" id="pvq-demo-pop" role="note" hidden>
  <div class="pvq-popover__body">…业务内容…</div>
  <div class="pvq-popover__foot">…可选脚注…</div>
  <span class="pvq-popover__arrow"></span>
</div>
类名 作用
.pvq-popover 浮层本体(block)
.pvq-popover__body 内容区
.pvq-popover__foot 脚注区(可选,border-top 分隔)
.pvq-popover__arrow 可选小箭头(JS 自动对准锚点中心)
.pvq-popover--open 打开态(由 JS 添加/移除)
.pvq-popover--above 由 JS 写在锚点上方时添加
  • 库不定义任何内容槽(无 __title / __icon)——内容是业务的,浮层只给外壳与脚注分隔。
  • 浮层不设背景遮罩、不做焦点陷阱(它不是模态)。

4. 定位契约

  • position: fixed;坐标由库 JS 用 getBoundingClientRect() 计算后写入内联 top/left。
  • 不依赖任何祖先的 position——锚点浮层若用 position:absolute 就必须假定业务容器是定位上下文,而那是业务的自由,库不该假定。
  • 默认贴锚点下方,间距 gap(默认 8px);下方空间不足且上方放得下 → 翻到上方并加 --above。
  • 水平夹取(左右各留 gap)、垂直同样不越界(top 夹在 gap ~ vh-gap-height)。
  • 宽度上限 min(24rem, calc(100vw - 2rem));z-index: 90。
  • 打开态下锚点滚动(捕获阶段监听,含任意滚动容器)/ 窗口缩放 → 重新计算坐标。

已知边界(写进文档,不让调用方踩):祖先若带 transform / filter / backdrop-filter / will-change,会成为 position:fixed 的包含块而使坐标偏移。库不移动浮层 DOM(保持最小侵入);这类容器里使用需调用方把浮层放到该容器之外。


5. 交互契约

触发 行为
mouseenter / mouseleave(锚点) 打开 / 延时 closeDelay(默认 160ms) 关闭,给鼠标移入浮层留时间
mouseenter / mouseleave(浮层) 取消关闭计时 / 延时关闭
focusin / focusout(锚点) 打开 / 焦点既不在锚点也不在浮层内时关闭
点击锚点 开合切换(触屏主路径)
Escape 关闭并把焦点交还锚点
点击浮层与锚点之外 关闭
  • 触屏与桌面用 matchMedia('(hover: hover)') 区分——触屏即便上报 hover 也常是「伪悬停」(首次点按才触发),不能直接信。
  • 关闭有退出过渡(EXIT_MS = 180ms,与 CSS transition: … .18s 对齐),过渡走完才置 hidden,不掐断动画。
  • prefers-reduced-motion: reduce → 禁用过渡与位移。

6. JS 契约

6.1 命令式

const pop = PVQUI.popover( anchorEl, {
  content: '<p>…</p>',   // 字符串 → innerHTML;或传 DOM 节点
  target: null,          // 可选:既有 .pvq-popover 元素(不传则由库创建并挂 <body>)
  placement: 'auto',     // auto | below | above
  gap: 8,
  closeDelay: 160,
  role: 'note',          // 由库创建时写入的 role,默认 note
  onOpen: fn, onClose: fn
} );
pop.open(); pop.close(); pop.toggle(); pop.destroy();
  • 返回值即实例:{ el, anchor, open, close, toggle, destroy }。
  • ⚠️ content 为字符串时按 HTML 插入(点评内容可能含格式,需求明确要求)。调用方不得把未经转义的用户输入拼进来——XSS 责任在调用方;只处理可信内容时建议改传 DOM 节点。

6.2 声明式(零 JS 调用,后端渲染友好)

initPopovers()(随 initAll() 自动调用)扫描锚点 [data-pvq-popover]:

  • 值为目标浮层 id;值为空时回退取 aria-describedby。
  • 目标必须存在且带 .pvq-popover 类(避免误绑普通 aria-describedby 说明文本)。
  • 声明式只走默认参数(placement: auto、gap: 8、closeDelay: 160);要定制用命令式。

6.3 事件与幂等

  • 派发 pvq-popover:open / pvq-popover:close(bubbles: true,detail { popover, el, anchor, reason },reason ∈ pointer|focus|click|escape|outside|api)。库不自带任何业务语义。
  • 幂等:同一浮层只初始化一次(实例挂在 el.__pvqPopover);document / window 级监听(Escape、外部点击、scroll、resize)只绑一次(模块级标志),不随 initAll 在 resize 上重跑而叠加——这是 1.3.0 initConfirms() 幂等教训的直接落实。
  • destroy() 解绑该实例的全部监听并清理库创建的节点;清除 __pvqPopover 后可重新挂载。

7. 无障碍契约(硬性)

  • 锚点:tabindex="0" + aria-describedby="<浮层 id>"(HTML 契约,库不注入——库不猜业务语义,缺了就是契约违规)。
  • 浮层:role="note"。选 note(注释)而非 tooltip:tooltip 语义要求「纯说明文本、不含可交互内容」,而本组件允许浮层内含链接/按钮等富内容。
  • 浮层不是模态:不得设 aria-modal,不得做焦点陷阱。
  • Escape 关闭后焦点交还锚点。
  • 触屏无悬停,需使用方自备可见可点提示(伪元素文案属业务文案,库不生成)。

8. 明确不做(避免过度设计)

  • ❌ 不做业务内容结构(无 __title / __label / __icon 固定槽)。
  • ❌ 不做异步加载、不做点击外部导航、不做自动消失计时(那是 toast 的活)。
  • ❌ 不做拖拽、不做吸附、不做多层嵌套浮层、不做 position:absolute 定位模式。

9. 与插件侧现状的差异(收敛对照)

项 插件侧现状(pvq-weixinyuedu) 库定稿
类名 .pvq-wr-thought / __trigger / __label / __body / __foot / __time / --open 外壳 .pvq-popover / __body / __foot / __arrow / --open / --above;.pvq-wr-thought 只留业务内容样式
显隐分支 .pvq-wr-js(head 同步脚本加,消 FOUC) 不需要——库默认即浮层态(见 §2.4),thought-boot.js 可删
定位/交互 thought.js 自实现(place() / 翻转 / CLOSE_DELAY / EXIT_MS) 删掉,改调 PVQUI.popover() 或声明式 data-pvq-popover
触发属性 data-pvq-thought-trigger + aria-describedby 绑定 data-pvq-popover="<id>"(aria-describedby 仍被兼容)
事件 无 新增 pvq-popover:open / pvq-popover:close
无 JS 静态块跟随文档流可见 库不提供该兜底(见 §2.4 取舍)

收敛判定:components.json 出现 popover 条目后,插件按《项目规则》§8 执行「删插件副本 + grep 防回潮校验」。当前为 experimental,插件侧建议先完成收敛再等库转 stable。


10. 验收标准

  • [ ] components.json 可见 popover 条目,官网画廊自动渲染(含可真实悬停的 demo)。
  • [ ] 类名全部 BEM 双横线,无单横线修饰符。
  • [ ] popover 段 grep -E '#[0-9a-fA-F]{3,8}|rgba?\(' assets/css/pvq-ui.css 零命中(零硬编码色值)。
  • [ ] 锚点滚动 / 窗口缩放时浮层跟随,不错位。
  • [ ] 视口边缘自动翻转与夹取,不溢出。
  • [ ] 键盘:锚点可聚焦、Escape 可关闭、焦点正确交还。
  • [ ] 不依赖任何祖先 position,无定位容器里坐标依然正确。
  • [ ] prefers-reduced-motion 下无动画。
  • [ ] initAll 反复调用(resize)不产生重复监听。

11. 本次同步的文档与产物

  1. assets/css/pvq-ui.css:新增 Popover 段 + @version → 1.3.1。
  2. assets/js/pvq-ui.js:新增 PVQUI.popover() / initPopovers(),并入 initAll + @version → 1.3.1。
  3. site/inc/components.json:新增 popover 条目 + version → 1.3.1。
  4. doc/模块文档/设计令牌与组件清单.md:补 API 条目 + 当前库版本。
  5. doc/开发文档/PVQ-UI组件库调用手册.md:新增 §4.7 调用接口 + 速查表一行。
  6. doc/开发文档/PVQ-UI组件库开发方案.md:§4.1 base 文件清单 + §7 JS 交互 + §3.1 v1.3.1 说明段。
  7. doc/开发文档/组件提取对照表.md:§2.16 新增(跨插件回流,非三套原有组件)。
  8. CHANGELOG.md:1.3.1 块(MINOR,向后兼容)。
  9. readme.md:当前版本 + 合规示例。
  10. examples/popover-demo.html:可交互验证页。
  11. 版本两 loader:pvq-ui-loader.php(仓库根,各插件复制源)与 site/pvq-ui-loader.php。
  12. site/inc/config.php:PVQ_UI_VERSION → 1.3.1。