canvas需用javascript自建group类模拟编组:通过本地坐标+全局变换实现渲染与交互;支持嵌套、逆变换拾取、统一控制(hide/settransform/select);注意矩阵缓存、包围盒优化及内存泄漏防范。

Canvas 本身不内置“对象编组”概念,所有绘制都是一次性像素操作。要实现类似 SVG 或 Figma 中的“组合/编组”行为(如整体拖拽、缩放、隐藏、统一变换),必须在 JavaScript 层构建逻辑层——也就是用数据结构管理对象关系,并同步控制渲染与交互。
用容器对象模拟编组
核心是创建一个 Group 类,它不直接绘制,而是持有子图形(如 Rect、Circle、Path 实例)并代理它们的坐标、变换和事件响应。
- 每个子对象保存相对于组原点的本地坐标(
x/y),组自身维护全局位移(group.x,group.y)、旋转(group.rotation)和缩放(group.scaleX/scaleY) - 渲染时,先用
ctx.save()+ctx.translate()+ctx.rotate()+ctx.scale()建立组坐标系,再遍历子对象调用其draw(ctx)方法(此时子对象按本地坐标绘制) - 鼠标拾取需做逆变换:将屏幕坐标减去组位置,再反向旋转/缩放,得到本地坐标后交由子对象判断是否命中
支持嵌套编组与层级管理
Group 可以作为另一个 Group 的子项,形成树状结构。关键在于递归处理变换矩阵和命中测试:
- 为每个对象(含 Group)分配唯一 ID,并维护一个全局
scene对象,提供find(id)和getTopmostAt(x, y)方法 - 渲染时深度优先遍历,每进入一层 Group 就
save(),退出时restore() - 点击检测从根 Group 开始,逐层将屏幕坐标转换为当前组的局部坐标;若某层未命中,则跳过其全部子项
统一控制与状态同步
编组的价值在于批量操作。Group 应暴露语义化方法,避免手动遍历:
-
group.hide()→ 设置visible = false并跳过渲染,不影响子对象独立可见性设置(可叠加) -
group.setTransform({x, y, rotation, scaleX, scaleY})→ 更新组级变换,子对象自动跟随(无需重设各自 transform) -
group.select()→ 触发高亮边框(用临时StrokeRect绘制包围盒,包围盒基于所有子项边界合并计算)
性能与边界注意事项
编组带来便利,也引入开销。实际开发中需注意:
- 避免频繁重建变换矩阵:缓存
group.getTransformMatrix()结果,在属性变更时标记dirty = true,下次渲染前更新 - 包围盒计算不要每次渲染都执行:仅当子对象位置/尺寸变动或组变换更新后重新计算
bounds - 删除编组时,确保清除对子对象的引用,防止内存泄漏;若子对象被多个组引用(如共享图标),需用弱引用或引用计数机制











