引言
大家好,我是 simple ,我的理想是利用科技手段来解决生活中遇到的各种问题。
恰巧前几天功能业务中也涉及到了这块,总结这篇文章,是因为代码中涉及到了坐标的转换和变换,包括缩放、平移等操作,这需要一定的数学基础和对Canvas坐标系统的理解。
绘制图像
在Canvas中绘制图像是一种常见的操作,可以通过创建一个Image对象并将其加载到Canvas中来实现。
javascript
// 创建Canvas元素和上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 创建Image对象并加载图像
const image = new Image();
image.src = 'image.jpg';
// 图像加载完成后绘制到Canvas上
image.onload = function() {
// 绘制图像到Canvas指定位置
ctx.drawImage(image, 0, 0);
// 可选:绘制图像并调整大小
// ctx.drawImage(image, 0, 0, 100, 100); // 在(0,0)位置绘制,宽高为100x100像素
};
drawImage
参数如下:
平移与缩放
js
ctx.drawImage(image, 10, 20, 30, 40);
image
:要绘制的图像,可以是<img>
、<canvas>
或<video>
元素。10
:绘制的目标位置的x坐标,即图像在Canvas中的左上角的x坐标。20
:绘制的目标位置的y坐标,即图像在Canvas中的左上角的y坐标。30
:绘制的目标宽度,即图像在Canvas中的宽度。40
:绘制的目标高度,即图像在Canvas中的高度。
了解上述参数后,那我们只需要获取鼠标按下事件的坐标,即可将图片进行拖拽,然后再于鼠标移动事件中不断更新画布移动照片,在鼠标松开事件中停止更新画布即可。鼠标滚轮的时候,我们只要调整新的参数即可对图像进行缩放。
到此正要大功告成之际,发现有一点需要优化的地方:鼠标滚轮缩放的时候,图像并不能根据鼠标位置进行缩放,而是会越偏越远。
这时我们就需要引进两个新的概念。
投影和反投影
投影是将画布上的坐标映射到实际图像上的坐标,而反投影则是将实际图像上的坐标映射到画布上的坐标。
假设我将一张 100x100 的照片放在一个 100x100 的画布上,并将照片放大了2倍,但画布大小不变。那么在画布的最右上角时,在画布的坐标为 (100, 0),而在照片中的坐标为 (50, 0)。
js
// 投影
project(v) {
const x = (v.x - this.centerPos.x) * this.scale;
const y = (v.y - this.centerPos.y) * this.scale;
return { x, y };
}
// 反投影
unproject(p) {
const x = (p.x / this.scale) + this.centerPos.x;
const y = (p.y / this.scale) + this.centerPos.y;
return { x, y };
}
这样的处理可以有效地在画布和实际图像之间进行坐标的转换,使得操作更加方便和直观。
滚轮缩放和鼠标位置
实现滚轮缩放和鼠标位置的基本思路是,监听鼠标滚轮事件,在事件处理函数中获取鼠标相对于 Canvas 的位置,并根据滚轮的方向和位置进行缩放操作。
将放大后的偏移量减去原始的偏移量,得到新的中心点相对于画布左上角的位置。这样就保证了画布在鼠标所在位置放大或缩小后,鼠标所在位置不变,从而形成了以鼠标为中心的缩放效果。
js
// 处理鼠标滚轮事件
handleMouseWheel(e) {
// t1表示鼠标位置在图像坐标系中的投影。
const t1 = this.unproject({ x: e.offsetX, y: e.offsetY });
if (e.deltaY < 0) {
this.scale *= 1.1;
this.centerPos.x = t1.x - (t1.x - this.centerPos.x) / 1.1;
this.centerPos.y = t1.y - (t1.y - this.centerPos.y) / 1.1;
} else {
this.scale /= 1.1;
// this.centerPos是图像的中心点坐标
// 实现以鼠标位置为中心进行缩放。
this.centerPos.x = t1.x - (t1.x - this.centerPos.x) * 1.1;
this.centerPos.y = t1.y - (t1.y - this.centerPos.y) * 1.1;
}
this.drawImage();
}
总结
时刻清晰canvas画布的坐标与鼠标坐标的关系,深入理解坐标映射关系及数学计算方法。
完整代码
js
export default class PictureRender {
constructor(canvas, options = {}) {
this.options = options;
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.centerPos = { x: 0, y: 0 };
this.scale = 1;
this.itscale = 1;
this.image = null;
this.isDrag = true;
// 为画布添加事件监听器
this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
this.canvas.addEventListener('wheel', this.handleMouseWheel.bind(this));
this.startButtonDownPos = {};
this.startButtonDownWP = {};
this.scaleWidth = 0;
this.scaleHeight = 0;
}
// 渲染图像
render(src) {
const image = new Image();
image.src = src;
this.image = image;
image.onload = () => {
const { width = 600 } = this.options;
this.itscale = width / this.image.width;
this.scaleWidth = this.image.width * this.itscale;
this.scaleHeight = this.image.height * this.itscale;
this.canvas.width = this.scaleWidth;
this.canvas.height = this.scaleHeight;
this.drawImage();
};
}
// 绘制图像
drawImage() {
this.canvas.width = this.scaleWidth;
this.canvas.height = this.scaleHeight;
const p1 = this.project({ x: 0, y: 0 });
const p2 = this.project({ x: this.image.width, y: this.image.height });
this.ctx.drawImage(this.image, p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
}
// 重置画布
reset() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 清空画布
this.ctx.setTransform(1, 0, 0, 1, 0, 0); // 重置变换矩阵
this.scale = 1;
this.startButtonDownPos = {};
this.centerPos = { x: 0, y: 0 };
this.drawImage();
}
// 处理鼠标按下事件
handleMouseDown(e) {
// 判断当前是点击事件还是拖动事件
this.isDrag = false;
this.startButtonDownPos = { x: e.clientX, y: e.clientY };
this.startButtonDownWP = { x: this.centerPos.x, y: this.centerPos.y };
this.isMouseDown = true;
}
// 处理鼠标抬起事件
handleMouseUp() {
this.isMouseDown = false;
}
// 处理鼠标移动事件
handleMouseMove(e) {
if (this.isMouseDown) {
const t1 = this.unproject({ x: e.clientX, y: e.clientY });
const t2 = this.unproject(this.startButtonDownPos);
const dx = t2.x - t1.x;
const dy = t2.y - t1.y;
this.centerPos.x = this.startButtonDownWP.x + dx;
this.centerPos.y = this.startButtonDownWP.y + dy;
this.drawImage();
}
}
// 处理鼠标滚轮事件
handleMouseWheel(e) {
const t1 = this.unproject({ x: e.offsetX, y: e.offsetY });
if (e.deltaY < 0) {
this.scale *= 1.1;
this.centerPos.x = t1.x - (t1.x - this.centerPos.x) / 1.1;
this.centerPos.y = t1.y - (t1.y - this.centerPos.y) / 1.1;
} else {
this.scale /= 1.1;
this.centerPos.x = t1.x - (t1.x - this.centerPos.x) * 1.1;
this.centerPos.y = t1.y - (t1.y - this.centerPos.y) * 1.1;
}
this.drawImage();
}
// 投影
project(v) {
const x = (v.x - this.centerPos.x) * this.scale * this.itscale;
const y = (v.y - this.centerPos.y) * this.scale * this.itscale;
return { x, y };
}
// 反投影
unproject(p) {
const x = (p.x) / (this.scale * this.itscale) + this.centerPos.x;
const y = (p.y) / (this.scale * this.itscale) + this.centerPos.y;
return { x, y };
}
}