笔记

拖一下画布,为什么撤销回不去了?

一个报表画布编辑器允许用户拖动矩形、文本、线段和图片,调整位置与大小。看起来是一次拖动,内部却同时涉及画布节点、文档数据、参考线和历史记录。

节点已经移动,不代表文档已提交新位置。每移动一个像素都记历史,撤销一次可能只退一小步;记录的又是可变对象,旧快照还可能被后续编辑改掉。

为什么不直接把节点状态全部存下来?

节点状态包含交互中的临时信息,文档则应保存重新加载后仍有意义的位置、尺寸和样式。把两者全部混在一起,辅助线、拖动偏移和页面缩放也可能进入历史。

项目把文档几何与临时交互分开。单元素通常在拖动结束时提交位置,多选先保留共同偏移,结束后把同一个 dx、dy 应用到所有元素,再清掉临时偏移。这样保持相对位置,也让一次拖动对应一次文档操作。

线段移动用节点临时 x、y,结束后把偏移加到两个端点,再将节点位置归零,防止下次叠加。端点拖动则有例外:它实时更新几何,第一次记录历史,后续更新不再新增记录。

缩放以后,为什么尺寸会重复变大?

画布节点可能同时保存 width、height 和 scale。如果先把 scale 乘进宽高,之后又继续保留原 scale,下一次变换或加载就可能重复放大。

项目在矩形缩放结束时把 scale 折进尺寸,再还原为 1,计算关系如下:

snippet
text
newWidth  = node.width  × node.scaleX
newHeight = node.height × node.scaleY
node.scale = (1, 1)

这套处理不直接套给所有元素。文本有排版高度和最小宽度,线段保存的是端点。屏幕像素、文档坐标和纸张毫米也先区分,网格步长按每毫米对应的像素比例换算。

参考线吸住了,松手后为什么又偏一点?

例如网格步长 10,元素拖到 x=98,附近参考边缘在 97。移动时吸到 97,结束时再按网格取整到 100,界面显示过对齐,最终却没有对齐。

解决思路是先约定两种吸附的优先级。项目按横纵轴分别处理:某轴命中参考线就保留该结果,没命中的轴再走网格。容差内选择更近候选,缩放时还看正在拖哪个手柄,避免拖右边缘却把左边一起移动。

因此吸附不只是计算一个最近坐标,还需要把命中结果带到提交阶段,防止后面再修正一次。

撤销历史保存快照,还是保存操作?

可以保存文档快照,撤销时直接恢复;也可以保存命令或差量,撤销时执行逆操作。前者容易实现,后者需要处理逆操作和合并,但能避免每步都保存完整状态。

原型先采用快照,只记录元素、纸张方向和选中项,不复制全部编辑器状态。撤销时把当前快照放进重做栈,再取出上一份;撤销后继续编辑会清空重做栈,恢复选中项时过滤不存在的元素。

已有用例覆盖吸附、选择、图片放置、序列化,以及部分复制粘贴与样式更新;一次手势撤销一次、连续端点拖动、旧快照不受污染等仍需分别补齐,真实指针事件还要到浏览器检查。

快照保存的是数组引用,没有深拷贝,正确性依赖不可变更新。后续代码若原地改端点数组或样式对象,历史也可能一起变。当前最多保留 100 步,限制的是步数,并没有限制每步文档和图片占多少内存。

返回上一页