
BlockSuite Frame Block 深度指南在 Edgeless 编辑器中标记画布区域、几何关联与演示模式【免费下载链接】blocksuite Content editing tech stack for the web - BlockSuite is a toolkit for building editors and collaborative applications.项目地址: https://gitcode.com/GitHub_Trending/bl/blocksuiteFrame Blockaffine:frame是 BlockSuite 中专门用于在 Edgeless白板编辑器里标记画布区域的块类型它不承载内部内容而是通过几何区域与周围元素建立打包关系并可作为演示Presentation模式的播放单元。本文以 packages/docs/components/blocks/frame-block.md 为骨架结合 frame-block 组件源码、模型定义、Frame 管理器与 E2E 测试带你从数据模型、渲染原理到实际操作完整掌握 Frame Block并理解它如何支撑演示模式下的视图切换。什么是 Frame BlockFrame Block 是一个用于在 Edgeless 编辑器中标记一块画布区域的块其核心定位可以从官方文档的说明中概括为三点区域标记它像取景框一样在无限画布上圈出一块矩形区域用于组织、定位和展示画布内容。几何关联而非嵌套当你拖动 Frame 时位于 Frame 内部的元素会跟随 Frame 一起移动。但注意Frame 内部并没有其他块——这种跟随效果基于 Frame 所覆盖的几何区域而不是模型层面的父子嵌套关系。演示模式驱动在演示模式下视口viewport会依次移动到各个 Frame 上实现一帧一页的幻灯片式浏览体验。也就是说Frame Block 本质上是一个分组容器但其分组语义完全建立在坐标几何之上这使它既轻量又灵活也让它成为 Edgeless 演示功能的核心载体。数据模型FrameBlockSchema与FrameBlockModelFrame Block 的模型定义位于 packages/affine/model/src/blocks/frame/frame-model.ts通过defineBlockSchema注册export const FrameBlockSchema defineBlockSchema({ flavour: affine:frame, props: (internal): FrameBlockProps ({ title: internal.Text(), background: --affine-palette-transparent, xywh: [0,0,100,100], index: a0, }), metadata: { version: 1, role: content, parent: [affine:surface], children: [], }, toModel: () { return new FrameBlockModel(); }, });其FrameBlockProps包含四个核心属性属性类型默认值说明titleText空文本Frame 的标题使用 BlockSuite 的 CRDT 富文本Text类型用于演示导航栏展示及标识backgroundColor--affine-palette-transparent背景色采用 CSS 变量形式的颜色 token默认为透明xywhSerializedXYWH[0,0,100,100]Frame 的位置与尺寸x, y, w, h在 Edgeless 中以模型坐标表示indexstringa0层级索引用于参与 Edgeless 的 z-index 排序值得注意的元数据细节role: content、children: []Frame不允许包含子块这正对应文档所说frame 内部没有其他块parent: [affine:surface]Frame 必须挂在 surface画布表面块下与形状、连接线等图形元素同级。FrameBlockModel继承自GfxCompatible(BlockModel)并实现GfxElementGeometry接口因此它既是一个标准块模型又能像图形元素一样参与 Edgeless 的几何运算。它重写了两个关键命中测试方法frame-model.tsincludesPoint(x, y, _)判断某点是否落在 Frame 边框附近5px 容差或标题区域externalBound内用于点击选中intersectsBound(selectedBound)判断选区边界是否与 Frame 相交或包含用于框选lasso/拖选逻辑。从源码结构可以推断这些几何接口正是Frame 与元素之间仅靠坐标区域交互这一设计的事实基础。渲染实现FrameBlockSpec与组件结构Frame Block 的 Spec 定义在 packages/blocks/src/frame-block/frame-spec.ts将 schema 与视图组件绑定export const FrameBlockSpec: BlockSpec { schema: FrameBlockSchema, view: { component: literalaffine-frame, }, };视图部分由 packages/blocks/src/frame-block/frame-block.ts 中的两个自定义元素构成FrameBlockComponentaffine-frame继承自GfxBlockComponentrenderGfxBlock渲染两块内容标题层edgeless-frame-title以极高的 z-index2147483647 - frameIndex绘制在 Frame 之上容器层.affine-frame-container宽高 100% 填充 Frame 区域带8px圆角背景色由ThemeObserver.generateColorProperty(model.background, ...)生成边框为2px solid var(--affine-black-30)。容器边框还有一个细节当处于演示导航模式frameNavigator或showBorder为 false 时边框会被隐藏置为none保证放映时不出现取景框干扰视觉。EdgelessFrameTitleedgeless-frame-title负责标题文本的渲染与跟随定位其内部实现展示了几个有意思的细节嵌套检测通过service.layer.framesGrid判断当前 Frame 是否位于另一个 Frame 内部_isInsideFrame并据此切换标题的摆放位置外层 Frame 标题显示在框外上方内层 Frame 标题显示在框内左上角偏移量为常量NESTED_FRAME_OFFSET 4缩放适配标题监听service.viewport.viewportUpdated以1/zoom反向缩放保证不同缩放级别下标题视觉尺寸恒定可见性控制当 Frame 高度过小32 / zoom bound.h且处于嵌套状态时隐藏标题避免遮挡响应式更新监听doc.slots.blockUpdated、model.propsUpdated、选中态变化以及工具切换frameNavigator等事件实时刷新。标题数据是 CRDT 文本this.model.title.yText.observe(updateTitle)直接观察Text的内部yText因此多人协同编辑标题时所有端都能即时同步。几何关联原理拖拽 Frame 时元素为何跟着移动文档强调关联效果基于几何区域而非模型嵌套其底层实现集中在 packages/blocks/src/root-block/edgeless/frame-manager.ts 的EdgelessFrameManager中。命中查询getElementsInFramegetElementsInFrame(frame: FrameBlockModel, fullyContained true) { const bound Bound.deserialize(frame.xywh); const elements: BlockSuite.EdgelessModel[] this._rootService.layer.canvasGrid.search(bound, true); return elements.concat( getBlocksInFrame(this._rootService.doc, frame, fullyContained) ); }它通过两种方式收集 Frame 内的元素canvasGrid.search(bound, true)从 surface 层的空间索引网格中查询几何边界与 Frame 相交的图形元素getBlocksInFrame/getNotesInFrame遍历affine:note等块模型按边界包含关系fullyContainedtrue时要求完全包含否则只要求左上角点在框内过滤。由此可以确认Frame 内的元素完全是由xywh边界计算出来的动态集合不存在任何持久化的父子引用。移动 Frame 时Edgeless 通过选中这些几何上命中的元素并整体平移来实现一起移动的效果——这正是 tests/edgeless/frame.spec.ts 中drag frame to move测试所验证的行为拖动 Frame 后原来位于其中的两个方形元素随之后移且各自坐标被精确断言[120, 20, 100, 100]。嵌套与高亮辅助frame-manager.ts还提供了若干配套工具removeContainedFrames(frames)过滤掉被其他 Frame 完全包含的嵌套 Frame避免演示时重复播放isFrameInner(frame, frames)判断某个 Frame 是否位于另一个 Frame 内部FrameOverlay一个绘制在 canvas 上的覆盖层以#1E96EB蓝色 2px 圆角矩形高亮目标 Frame用于创建或选中时的视觉反馈。创建 Frame 的四种方式与默认参数方式一组件工具栏 / 快捷键在 Edgeless 中多选元素后可通过组件工具栏的 add frame 或按快捷键F快速创建。createFrameOnSelectedframe-manager.ts的算法如下计算选中元素的外接边界edgelessElementsBound(...)向外扩展FRAME_PADDING 40即四周各留 40 单位内边距若宽度小于MIN_FRAME_WIDTH 800或高度小于MIN_FRAME_HEIGHT 640则居中扩充到最小尺寸以affine:frame创建新块标题自动命名为Frame ${frames.length 1}随后将该 Frame 设为选中。测试 tests/edgeless/frame.spec.ts 验证了这一点两个 100×100 的方形选中后创建 Frame其边界被断言为[-300, -270, 800, 640]——即被扩充到了最小尺寸 800×640 并居中。方式二Frame 工具拖拽绘制切换到 Edgeless 工具栏的 frame 工具后直接在画布上拖拽即可绘制自定义大小的 Frame。对应的控制器 frame-tool.ts 中拖拽距离小于 8px 会被忽略防止误触拖拽过程中通过stash(xywh)/pop(xywh)暂存与回滚坐标保证撤销undo语义正确松手后自动切回默认工具并选中新 Frame同时通过telemetryService上报CanvasElementAdded埋点。方式三Frame 菜单预设尺寸工具栏 frame 菜单还提供了预设尺寸如1:1对应测试中的data-name1:1按钮创建出[-500, -550, 1200, 1200]的 Frame方便快速生成标准画幅。编辑标题双击 Frame 标题即可进入edgeless-frame-title-editor内联编辑支持回车确认、失焦退出见 frame.spec.ts 中edit frame title、blur unmount frame editor等用例缩放后编辑同样可用edit frame after zoom。演示模式Presentation视口在 Frame 间切换文档最后一句指出在演示模式下视口会被移动以依次聚焦各个 Frame。这也是 Frame Block 在 BlockSuite 中最具代表性的应用场景。导航工具与控制器进入演示模式后工具类型切换为frameNavigator对应控制器 frame-navigator-tool.ts 中的PresentToolController。该控制器屏蔽了绝大部分鼠标交互点击、拖拽、右键等均为 noop只保留导航语义避免演示时误操作画布。视口聚焦与两种导航模式演示工具栏 presentation-toolbar.ts 的_moveToCurrentFrame是核心逻辑let bound Bound.deserialize(frame.xywh); if (this._navigatorMode fill) { // 按视口宽高比扩展 Frame 边界实现填充屏幕 ... } viewport.setViewportByBound(bound, [0, 0, 0, 0], false); this.edgeless.slots.navigatorFrameChanged.emit(frame);导航模式NavigatorMode定义在 packages/blocks/src/_common/edgeless/frame/consts.ts模式行为fit视口完整容纳 Frame 边界等比缩放到全部可见fill以 Frame 中心为基准、按视口宽高比扩展边界尽可能填满屏幕可能裁切边缘模式可通过演示工具栏的配置按钮切换对应navigatorSettingUpdated事件与presentFillScreen存储项。翻页、快捷键与全屏工具栏提供Previous / Next按钮到达首尾帧时通过toast提示全局快捷键←/→切换上一帧/下一帧仅在frameNavigator模式下生效全屏切换进入演示模式时自动全屏launchIntoFullscreen黑底遮罩层 edgeless-navigator-black-background.ts 会根据当前 Frame 的xywh与视口缩放计算屏幕上的渲染区域营造放映效果工具栏同时显示当前帧标题与进度当前序号 / 总帧数并支持edgeless-frame-order-button调整 Frame 的播放顺序。E2E 测试 tests/edgeless/presentation.spec.ts 完整验证了该流程创建两个 Frame一个包含 shape、一个包含 note后进入演示模式点击 next 使 note 可见、点击 previous 使其隐藏——直观印证了视口聚焦 Frame的行为。若画布上没有任何 Frame进入演示模式会提示需要至少一个 Frame。小结与延伸阅读Frame Block 的设计可以概括为一句话用最轻量的矩形边界在无限画布上实现打包、移动、放映三类能力。它没有复杂的子块层级靠纯几何计算完成元素聚合它又能无缝接入 Edgeless 的层级系统index、CRDT 文本title和空间索引canvasGrid这正是 BlockSuite块与图形元素统一建模思想的典型体现。想进一步深入可以在仓库中继续阅读模型与 Schemapackages/affine/model/src/blocks/frame/frame-model.ts组件与 Specpackages/blocks/src/frame-block/frame-block.ts、packages/blocks/src/frame-block/frame-spec.ts几何关联核心packages/blocks/src/root-block/edgeless/frame-manager.ts演示模式实现presentation-toolbar.ts、frame-navigator-tool.ts行为验证tests/edgeless/frame.spec.ts、tests/edgeless/presentation.spec.ts【免费下载链接】blocksuite Content editing tech stack for the web - BlockSuite is a toolkit for building editors and collaborative applications.项目地址: https://gitcode.com/GitHub_Trending/bl/blocksuite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考