04-Sandbox画布与交互
Sandbox 的真实职责
很多人第一次看这个仓库,会把 sandbox 当成 runtime。实际上它不是。
packages/sandbox 的职责是:
- 挂载 iframe
- 建立编辑器和 runtime 的通信桥
- 维护选中框、蒙层、辅助线、高亮
- 承接拖拽、缩放、多选、吸附等交互
它是“编辑态交互层”,不是业务页面执行层。
BoxCore 是对外总控
BoxCore 统一管理三个子模块:
BoxRender- iframe 渲染和 runtime 桥接
BoxMask- 蒙层、遮罩、选中框、参考线
ActionManager- 鼠标键盘交互、拖拽缩放、多选、高亮
所以理解 sandbox,最好的办法不是从 UI 看,而是从这三个模块的职责边界看。
iframe 为什么是必须的
项目用 iframe 不是为了炫技,而是为了做隔离。
它至少解决了 4 个问题:
- 编辑器样式不会污染业务页面
- 业务组件事件不会直接打到编辑器壳层
- 不同 runtime 可以独立演进
- 选中、高亮、拖拽可以在蒙层上做,不必入侵业务组件内部
这也是该项目能够同时支持 Vue2 / Vue3 runtime 的基础之一。
编辑器和 runtime 怎么握手
握手流程在 BoxRender 和 runtime playground App 里最清楚:
BoxRender.mount创建 iframe- 给 iframe 的
window注入window.quantum - runtime 启动后调用
window.quantum.onRuntimeReady(...) - runtime 把自己的能力暴露出来
暴露的核心接口包括:
getAppupdateRootConfigupdatePageFieldselectaddupdatedelete
这套接口非常关键,它定义了“编辑态如何驱动运行态”。
为什么还要有 BoxMask
因为编辑器需要处理的是“编辑交互”,而不是“真实点击组件”。
例如:
- 选中一个按钮时,你不希望触发按钮业务点击逻辑
- 拖拽一个元素时,你要拖的是它的编辑框,不是组件自己的鼠标事件
- 多选、辅助线、容器高亮这些能力,业务组件本身并不具备
所以项目在 iframe 上方放了一层 mask,把编辑交互拦截出来单独处理。
ActionManager 为什么复杂
因为它要解决的是低代码编辑器里最麻烦的一类问题:编辑坐标系和业务 DOM 坐标系的同步。
它要同时处理:
- 单选
- 多选
- 高亮
- 拖拽
- 缩放
- 旋转
- 键盘辅助
- 容器悬停高亮
这类逻辑天然比普通页面交互复杂。
坐标换算是这一层的核心难点
项目存储的是设计稿坐标,但用户操作的是当前画布像素。
所以拖入新组件时会发生一系列换算:
- 取鼠标在 sandbox 里的可视坐标
- 加上滚动偏移
- 根据设计稿宽度换算成 Schema 存储值
- 再根据父容器布局修正相对位置
这就是 packages/editor/src/components/layouts/sandbox/index.vue 中那段坐标计算代码存在的根本原因。
为什么选中态有两份
你会看到选中相关状态分散在两边:
EditorService- 关心“当前业务上选中了哪个节点”
Sandbox / ActionManager- 关心“当前 DOM 层选中了哪个元素”
这不是重复,而是两个不同层次:
- 一个是数据层选中
- 一个是交互层选中
两者需要同步,但不能混为一谈。
Sandbox 这层的典型问题怎么查
现象 1:节点明明存在,但选不中
优先检查:
- DOM 是否有对应
id getTargetElement是否能查到元素- mask 是否已挂载
- 是否被别的元素挡住
现象 2:拖拽后位置不对
优先检查:
- 当前父布局是
relative / absolute / fixed哪种 designWidth是否一致- 当前 zoom 是否参与了换算
现象 3:iframe 里页面显示对了,但编辑器框选错位
优先检查:
onPageElUpdate是否正确触发- page 元素尺寸是否已同步给 mask
- zoom 和 scrollTop/scrollLeft 是否同时参与了计算
对新人最重要的结论
遇到画布问题时,不要直接去怀疑 Vue 渲染,先判断它属于下面哪一类:
- Schema 不对
- runtime 没同步
- iframe DOM 对了,但 mask/Moveable 没对齐
第三类问题才是 sandbox 自己的问题。
