Fabric.js 图层的顺序管理:置顶、置底、上移、下移
图层面板是画布编辑器的标配。别小看 图层排序 这几个按钮,一旦背景层、工作区层参与其中,顺序管理就很容易出乱子。这篇文章把置顶/置底/上移/下移的正确写法,以及 保护关键层 的坑写清楚。

一、Fabric 的排序 API
Fabric 对象有 4 个经典层级方法,本质是调整对象在 canvas.getObjects() 数组里的下标:
bringToFront():到最顶层sendToBack():到最底层bringForward():上移一层sendBackwards():下移一层
用法极其简单:
selected.sendToBack(); // 点"置底"
selected.bringToFront(); // 点"置顶"
selected.bringForward(); // 点"上一层"
selected.sendBackwards();// 点"下一层"二、问题:背景层和工作区层会被误动
如果你的画布里有"工作区(workspace)"和"背景(background)"这种特殊层,它们往往必须固定在最底层或锁定各自顺序。如果用户把某张图"置到底",它可能把 workspace 和 background 又抬到上面来了——因为 sendToBack 会把它放到数组最前面。
解决思路是在每次排序后,把关键层"强制回位":
function sortKeyLayers() {
if (background) background.sendToBack(); // 背景永远在最底
if (workspace) workspace.sendToBack(); // 工作区次底
if (background) background.bringForward(); // 让背景介于 workspace 之下/之上按需
}规约很清晰: 背景永远垫底,工作区在其上,业务图层在工作区之上 。每次排序后调用一次即可保证关键层不被顶乱。
二、命令的上移/下移要循环处理
bringForward / sendBackwards 一次只移一层。如果要支持"自定义拖拽排序"的效果(把层 A 拖到层 B 的位置),就得计算位移次数并循环:
// 假设把 newIndex 位置的层进行排序
function moveBySteps(layer, step) {
const step = Math.abs(step);
for (let i = 0; i < step; i++) {
if (step > 0) layer.bringForward(); else layer.sendBackwards();
}
canvas.requestRenderAll();
}四、用数组索引直接排序更高效
靠 bringForward 一次次挪,层多时性能堪忧。更稳妥的方法是基于 canvas.getObjects() 数组本身重组,再 canvas._objects.sort (Fabric 内部有私有数组但推荐用公开方式)。若只是几个按钮,循环 acceptable;若是拖拽排序,建议走数组重组。
五、与历史记录联动
排序是可撤销的。所以在完成一次排序后,建议推入历史快照,让"撤销/重做"也能恢复层级:
function pushHistory() {
if (!here.changeHistory) return;
const d = canvas.toDatalessJSON();
// 和时间戳做去重,若与上一笔一致则不重复记录
const hash = md5(JSON.stringify(d));
if (history.length && hash === history[history.length - 1].hash) return;
history.push({ hash, data: d });
historyIndex = history.length - 1;
}六、避坑
- 置底被关键层"反弹" :见第二节,排序后强制回位关键层。
- 多层置顶/置底语义 :多个层同时置底,它们在底层内的相对顺序可能被打乱。处理多选时先按数组下标排序,保证底端相对顺序稳定。
sendBackwards与梯形关系 :如果有多个互相遮挡的图形,"置底但只压低一层"的诡异表现,多半是moveTo(index)用错。moveTo目标 index 要基于getObjects()下标精确算。- sortable 插件联动 :用 Drag 对层列表改动时,要把 UI 的 newIndex 换算成
moveTo的下标,否则 UI 顺序和 canvas 顺序不一致。
七、验证
构造 workspace + background + 三个矩形。分别点击"置顶/置底/上移/下移",验证:关键层始终在最底且相对顺序不变、业务层按预期改变遮挡关系、撤销能恢复原顺序。这套逻辑理解透了,层级面板就稳了。
FAQ 常见问题
问:用
sendToBack置底,background 也跟着跑了怎么办?
Fabric 的sendToBack/sendBackwards会把对象压到最底,可能挤到背景/工作区之下。排序后要再把关键层强制bringToFront/ 重新moveTo到它们应有的位置,保证背景不被误伤。问:多个图层同时"置底",它们底下的相对顺序乱怎么办?
多选置底前先按getObjects()的下标排好序,再逐个下移,这样它们在底层内能保持相对稳定,不会出现 A/B 顺序被翻转。问:为什么"只置下一层"换个图形就多了好几层?
各位多半是moveTo的目标 index 算错了。moveTo(index)的下标要基于getObjects()的实时下标精确计算,而不是凭直觉数;否则遮挡关系会错位。






