用原生 Canvas 实现拖拽与缩放
一个关于无限画布的教程,帮助开发者理解和实现无限画布的概念与功能。
无限画布是一种允许用户以非线性方式自由组织内容的界面,支持缩放、直观编辑基本图形(如移动、分组和修改样式)等功能。”
当然可以!我来给你分成三部分讲解 xiaoiver/infinite-canvas-tutorial 项目的核心内容,让你更清晰地理解它的设计思路:
1. 项目目的:打造一个基本的「无限画布」
- 目标是让画布可以被拖动(pan)、缩放(zoom),并且画面上的元素(比如点、矩形)随着拖拽或缩放正确跟随变换。
- 重点是操作的是视图,而不是操作元素本身。也就是说,图形坐标是固定的,用户是在「移动摄像机」看画布。
2. 主要功能点讲解
功能 | 实现思路 | 细节 |
---|---|---|
拖动画布 | 监听 mousedown , mousemove , mouseup 事件,计算鼠标移动量,更新平移值(offsetX , offsetY ) |
用变量记录当前平移状态 |
缩放画布 | 监听 wheel 滚轮事件,根据滚轮方向放大或缩小,调整 scale 缩放比例 |
注意缩放时要以鼠标指针为中心调整偏移 |
绘制图形 | 每次 draw() 函数调用时,根据当前的 offsetX , offsetY , scale 重新绘制画布上的元素 |
要在绘制前使用 Canvas 的 ctx.setTransform() 应用变换 |
保持变换状态 | 使用 ctx.save() 和 ctx.restore() 方法,确保每次绘制时 Canvas 状态正确 |
避免变换叠加出错 |
3. 核心代码逻辑梳理
一个标准的渲染流程大概是这样:
function draw() {
ctx.save(); // 保存当前变换状态
ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY); // 应用平移和缩放
ctx.clearRect(-offsetX/scale, -offsetY/scale, canvas.width/scale, canvas.height/scale); // 清除画布
// 绘制固定在世界坐标下的图形
drawAllElements();
ctx.restore(); // 恢复变换状态
}
scale
控制缩放大小。offsetX
和offsetY
控制画布的平移。- 每次绘制前,先应用缩放和平移的变换,再绘制图形。
交互时,例如:
- 鼠标拖动 -> 更新
offsetX/offsetY
- 鼠标滚轮 -> 更新
scale
- 然后重新调用
draw()
重绘
补充细节
- 画布初始化时,需要根据设备像素比(DPR)调整
canvas.width
和canvas.height
,保证高分屏下画质清晰。 - 缩放时为了做到以鼠标指针为中心,需要用一些简单的坐标换算公式。
小结
这个项目可以看作是:
- 极简版白板引擎
- 交互式 2D 编辑器的基础
- 理解 Canvas 变换矩阵的实践
它只是基础版,但如果想扩展,比如加上:
- 元素选择、拖拽、删除
- 连线、分组
- 多人协作同步
- 保存和恢复画布内容
就可以慢慢做成类似 Figma / Miro / Excalidraw 这样的复杂应用了!