npm.io
0.1.0 • Published 1 month ago

@dimina-kit/view-anchor

Licence
MIT
Version
0.1.0
Deps
0
Size
269 kB
Vulns
0
Weekly
0
Stars
6

@dimina-kit/view-anchor

让一块主进程原生视图(Electron WebContentsView)始终贴住某个 DOM 元素的屏幕矩形,一对一绑定。

DOM 布局库(flexbox、dockview、react-resizable-panels……)都不管渲染进程与主进程之间那道边界,它们移动的永远只是 DOM 节点。view-anchor 就是跨过这道边界的桥:它测量目标元素的 getBoundingClientRect(),把得到的矩形交给一个 publish 回调(由你接上 IPC → setBounds),并在元素发生位移或缩放时重新发布。

核心不依赖 React、Electron 或任何宿主布局引擎。涉及 React 的代码只在适配层。

安装

pnpm add @dimina-kit/view-anchor

快速上手

命令式核心:

import { createViewAnchor } from '@dimina-kit/view-anchor'

const handle = createViewAnchor(target, {
  present: true,                 // 挂载原生视图
  publish: (bounds) => { ... },  // 接收实时矩形;由它负责 IPC → setBounds
})

handle.update({ present, publish }) // 应用新选项(会立即重新发布)
handle.dispose()                    // 停止观察;此后不再发布

React 适配层:

import { useViewAnchor } from '@dimina-kit/view-anchor'

function DebugPanel({ visible }: { visible: boolean }) {
  const ref = useViewAnchor({
    present: visible,
    publish: publishSimulatorDevtoolsBounds,
  })
  // 原生视图会跟随这个占位 div;隐藏面板
  // (visible=false 或卸载)会让它收起,但不销毁。
  return <div ref={ref} className="h-full w-full" />
}

工作原理(一句话版)

  • present: true:立即发布测量矩形,之后在每次 ResizeObserver 触发、窗口 resize同步重新发布(不走 RAF——原生 overlay 的 setBounds 本就慢一帧,RAF 再叠一帧会拖尾)。与上次逐字段相同的矩形会被去重跳过。x/y 取整但允许负(滚出边缘时跟随),width/height 钳到 ≥0。
  • present: false:发布一次 {0,0,0,0} 并停止观察(已有的观察器一并拆除)。宿主把「面积为零」读作「摘除子视图,但保留其 WebContents 存活」,即收起而非销毁。
  • dispose() 之后不再发布任何内容,也不会补发一帧零矩形(那是调用方的责任;React 适配层已替你在元素消失时处理好)。

反向:createSizeAdvertiser(内容尺寸回流)

正向让原生视图跟随 DOM;反向让 DOM 占位跟随内容。当一块 WebContentsView 的尺寸由它自己的内容主导时(例如交给下游控制的 toolbar),在下游视图自己的渲染进程里跑:

import { createSizeAdvertiser } from '@dimina-kit/view-anchor'

const handle = createSizeAdvertiser(contentWrapper, {
  axis: 'block',                 // 这个 advertiser 只主导一条轴(block=高 / inline=宽),不可变
  publish: (size) => { ... },    // 接收 { axis, extent },由它负责 IPC → 宿主
})

handle.update(publish) // 换 publish(IPC 通道),并立即把当前尺寸发给它
handle.dispose()       // 停止观察;此后不再上报

它从 ResizeObserver 的 border-box 取主导轴尺寸(不调 getBoundingClientRect、不强制 reflow),Math.round + 钳零,经反向专用的 RAF 合并 + 去重发出(正向同步、反向 RAF,刻意不对称)。宿主收到尺寸后调整占位、再由正向把视图贴上去——两个单向原语经占位 div 串成一座双向桥。注意 footguntarget 必须在主导轴上 shrink-to-fit(其尺寸不能被宿主灌入的视图尺寸反向决定),否则跨进程环不收敛。详见 docs/bidirectional-design.md

文档

  • docs/mechanism.mdx —— 正向完整机制:同步发布与陈旧帧安全性、present / 零矩形 / 卸载契约、React 18 StrictMode 生命周期。内嵌可交互 3D 演示 docs/anchor-3d.html
  • docs/bidirectional-design.md —— 双向几何桥:正向同步 / 反向 RAF 的刻意不对称、单轴所有权与收敛性、信任边界、以及两个原语如何「锚」到一起。

API

导出 类型 作用
createViewAnchor(target, opts) 函数 正向命令式核心,返回 { update, dispose }present:false 用零矩形 {0,0,0,0} 表示收起。不依赖 React、不依赖 Electron。
createPlacementAnchor(target, opts) 函数 正向核心的显式 Placement 变体:可见性是判别式 { visible:true, bounds } / { visible:false },绝不从零尺寸推断——真正 0×0 但在屏的视图与隐藏视图就此可区分。另支持 opt-in 的 guardDisplayNone / followScroll / followGeometry(按需开窗的 RAF 几何哨兵)+ pulse()
measurePlacement(target) 函数 纯测量:读 target 矩形,包成 { visible:true, bounds }
useViewAnchor(opts) Hook 正向 React 适配层(基于 createViewAnchor),返回一个挂到占位元素上的 ref 回调。
createSizeAdvertiser(target, opts) 函数 反向命令式核心,返回 { update, dispose }。下游量内容尺寸回流给宿主。
Bounds 类型 { x, y, width, height },单位为 CSS 像素。
Placement 类型 { visible:true; bounds:Bounds } | { visible:false },显式可见性判别式。
ViewAnchorOptions / ViewAnchorHandle 类型 正向零矩形核心的选项与句柄形状。
PlacementAnchorOptions / PlacementAnchorHandle 类型 正向 Placement 核心的选项与句柄形状。
UseViewAnchorOptions / ViewAnchorRef 类型 正向适配层的选项与 ref 回调形状。
AdvertisedAxis / AdvertisedSize 类型 反向的轴('block'|'inline')与帧载荷 { axis, extent }
SizeAdvertiserOptions / SizeAdvertiserHandle 类型 反向核心的选项与句柄形状。

Keywords