本文会带大家使用 TypeScript 封装一个构造函数 CanvasContainer。用来实现 Canvas 画布的 拖动平移 和 鼠标滚动缩放 两个常用的功能。
二、Canvas 画布:绘制直线、矩形、多边形 👉👉👉 代码演示
三、Canvas 画布:图形的选中和移动 (上) 👉👉👉 代码演示
四、Canvas 画布:图形的选中和移动 (下) 👉👉👉 代码演示
五、Canvas 画布:修改图形各端点的位置 👉👉👉 代码演示
六、Canvas 画布:图形移动时的辅助线和吸附效果 👉👉👉 代码演示
1. 创建一个 CanvasContainer 构造函数
第一步:创建一个构造函数 CanvasContainer
,它接受一个参数:canvas 画布。并初始化一些会用到的变量。
class CanvasContainer {
/** 画布的宽度 */
public width = 0;
/** 画布的高度 */
public height = 0;
/** 画布的上下文 */
public ctx = null as unknown as CanvasRenderingContext2D;
private mouseDownOffsetX = 0; // 鼠标按下时,鼠标的位置
private mouseDownOffsetY = 0;
private offsetX = 0; // 当前正在拖动的偏移量
private offsetY = 0;
private preOffsetX = 0; // 上一次移动结束后的偏移量
private preOffsetY = 0;
private mouseWheelOffsetX = 0; // 鼠标滚轮滚动时,鼠标的位置
private mouseWheelOffsetY = 0;
/** 缩放比例 */
public scale = 1;
private preScale = 1; // 上一次的缩放比例
private scaleStep = 0.1; // 每次缩放的间隔
private maxScale = 5; // 最大缩放比例
private minScale = 0.2; // 最小缩放比例
/**
* 创建 Canvas 画布
* @param canvas canvas标签
*/
constructor(public canvas: HTMLCanvasElement) {}
}
2. 初始化 Canvas 画布
第二步:对画布进行初始化。
- 获取到 canvas 画布的上下文。
- 设置 canvas 画布的宽高。canvas有两个宽高:
- 一个是 canvas 画布在显示屏上的宽高,一般称画布尺寸。
- 一个是 canvas 画布自身绘图时的宽高,一般称画板尺寸。
- 为避免图形变形失真,在设备的 devicePixelRatio 为 1 时,要保持两个宽高相同;在不为 1 时,要将画板的尺寸缩放。
/** 初始化 Canvas */
private initCanvas = () => {
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
const { width, height } = this.canvas.getBoundingClientRect();
this.width = width;
this.height = height;
/** 避免图形变形失真 */
this.ratio = window.devicePixelRatio || 1;
/** 设置画布绘制图形区域的尺寸,默认为 300 * 150 */
this.canvas.width = this.width * this.ratio;
this.canvas.height = this.height * this.ratio;
this.ctx.scale(this.ratio, this.ratio);
}
3. 在 Canvas 画布上渲染一些图形
第三步:在画布上渲染一些图形,以便于演示。
- 绘制一个 矩形 和 圆形。
- 矩形:
this.ctx.rect(300, 100, 200, 100)
; - 圆形:
this.ctx.arc(100, 100, 30, 0, Math.PI * 2)
;
- 矩形:
- 在每次渲染之前要先清空画布。
/** 渲染函数 */
render = () => {
/** 清空画布 */
this.ctx.clearRect(0, 0, this.width * this.ratio, this.height * this.ratio);
/** 保存画布状态 */
this.ctx.save();
/** 平移画布 */
this.ctx.translate(this.offsetX , this.offsetY);
/** 缩放画布 */
this.ctx.scale(this.scale, this.scale);
/** 渲染图形 */
this.ctx.beginPath();
this.ctx.rect(300, 100, 200, 100);
this.ctx.arc(100, 100, 30, 0, Math.PI * 2);
this.ctx.closePath();
this.ctx.fill();
/** 移除画布状态 */
this.ctx.restore();
}
4. 添加鼠标的按下、移动、抬起事件,实现画布的移动
第四步:给画布添加鼠标的按下、移动、抬起、离开事件,用来移动画布。
画布的平移使用到了画布的API translate
,将画布的原点移动到指定位置。
- 给画布添加鼠标按下事件,用于记录元素开始移动时鼠标的位置。
- 给画布添加鼠标抬起事件,用于记录元素结束移动时鼠标的位置。
- 给画布添加鼠标移动事件,用于记录元素的当前位置。
- 元素当前的位置 = 当前鼠标的位置 - 鼠标开始移动时的位置 + 元素上一次移动结束后的位置
- 在鼠标离开画布时要取消鼠标的事件。
/** 添加事件函数 */
private addEvent = () => {
/** this.canvas.addEventListener('wheel', this.onMouseWheel, { passive: true }); */
this.canvas.addEventListener('mousedown', this.onMouseDown);
this.canvas.addEventListener('mouseleave', this.onMouseUp);
this.canvas.addEventListener('mouseleave', this.onMouseLeave);
}
/** 鼠标按下事件 */
private onMouseDown = (event: MouseEvent) => {
// 鼠标左键
if (event.button === 0) {
// 记录鼠标按下时,鼠标的位置
this.mouseDownOffsetX = event.x;
this.mouseDownOffsetY = event.y;
// 添加事件
this.canvas.addEventListener('mousemove', this.onMouseMove);
this.canvas.addEventListener('mouseup', this.onMouseUp);
}
}
/** 鼠标按下移动事件 */
private onMouseMove = (event: MouseEvent) => {
// 鼠标按下后的移动偏移量 = 当前鼠标的位置 - 鼠标按下的位置
// 当前拖动偏移量 = 上一次的偏移量 + 鼠标按下后的移动偏移量
this.offsetX = this.preOffsetX + (event.x - this.mouseDownOffsetX);
this.offsetY = this.preOffsetY + (event.y - this.mouseDownOffsetY);
// 重新渲染
this.render();
}
/** 鼠标抬起事件 */
private onMouseUp = () => {
// 记录鼠标抬起时,鼠标的位置
this.preOffsetX = this.offsetX;
this.preOffsetY = this.offsetY;
// 移除事件
this.canvas.removeEventListener('mousemove', this.onMouseMove);
this.canvas.removeEventListener('mouseup', this.onMouseUp);
}
/** 鼠标离开事件 */
private onMouseLeave = () => {
this.onMouseUp();
}
5. 添加鼠标滚轮的滚动事件,实现画布的缩放
第五步:给画布添加鼠标滚轮的滚动事件,用于画布的缩放。
画布的缩放使用的是画布的API
scale
,将画布缩放为 x, y。
- 记录滚轮滚动时,鼠标的位置。
this.mouseWheelOffsetX
- 计算当前的缩放比例,并求出缩放比。
zoomRatio
- 计算缩放之前,鼠标到画布原点的距离。
this.mouseWheelOffsetX - this.offsetX
- 计算缩放之后,鼠标到画布原点的距离。
(this.mouseWheelOffsetX - this.offsetX) * zoomRatio
- 计算新的原点的位置:鼠标的位置 - 画布缩放之后鼠标到原点的距离。
this.mouseWheelOffsetX - (this.mouseWheelOffsetX - this.offsetX) * zoomRatio
- 将当前的缩放比例,画布原点位置,记录为上一次的缩放比例,画布原点位置。
/** 鼠标滚轮滚动事件 */
private onMouseWheel = (event: WheelEvent) => {
// 获取鼠标滚轮滚动时,鼠标的位置
this.mouseWheelOffsetX = event.offsetX;
this.mouseWheelOffsetY = event.offsetY;
if (event.deltaY < 0) {
// 画布放大
if (this.scale >= this.maxScale) return;
// 解决小数点运算丢失精度的问题
this.scale = parseFloat((this.scale + this.scaleStep).toFixed(2));
} else {
// 画布缩小
if (this.scale <= this.minScale) return;
// 解决小数点运算丢失精度的问题
this.scale = parseFloat((this.scale - this.scaleStep).toFixed(2));
}
// 缩放比
const zoomRatio = this.scale / this.preScale;
// 鼠标当前的位置 - 当前拖动偏移量
this.offsetX = this.mouseWheelOffsetX - (this.mouseWheelOffsetX - this.offsetX) * zoomRatio;
this.offsetY = this.mouseWheelOffsetY - (this.mouseWheelOffsetY - this.offsetY) * zoomRatio;
this.render();
// 将当前的缩放比例保存为 上一次的缩放比例
this.preScale = this.scale;
// 记录鼠标滚轮停止时,鼠标的位置
this.preOffsetX = this.offsetX;
this.preOffsetY = this.offsetY;
}