在一些实际的应用场景中,我们需要对照片添加一些贴纸的功能:
- 拖动贴纸到图片上
- 针对贴纸进行移动,缩放和旋转的操作
这些功能我们都用 canvas 来实现。
下面是简单的代码结构👇
<div id="draggable" draggable="true">Drag me</div>
<canvas id="canvas" width="600" height="300"></canvas>
#draggable {
width: 100px;
height: 100px;
background-color: red;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 24px;
cursor: move;
}
#canvas {
border: 1px solid black;
}
这里,为了方便演示,我们使用一个
div元素来模拟贴纸img
Canvas 绘制图片
在 canvas 绘制图片,我们可以使用 drawImage 方法来实现 👇
const image = new Image(); // 创建一个图片对象
img.src = "path/to/your/image.png";
// 图片加载完后操作
img.onload = () => {
// 将贴纸绘制在 canvas 上
ctx.drawImage(img, 0, 0, 300, 300);
}
// 图片加载错误
img.onerror = () => {
console.error("load image error");
}
拖动贴纸到 Canvas 上
我们拖动贴纸到指定的 canvas 上,我们这里只演示鼠标拖动的功能,如果需要添加触摸的功能,可以参考鼠标功能自行添加。
const draggable = document.getElementById("draggable");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
draggable.addEventListener("dragstart", (e) => {
// 需要设置一些数据才能触发拖放事件
e.dataTransfer.setData('text/plain', null);
});
canvas.addEventListener("dragover", (e) => {
// 防止默认行为,使得drop事件可以被触发
e.preventDefault();
});
canvas.addEventListener("drop", (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 在 canvas 上绘制红色的矩形
ctx.fillStyle = "red";
ctx.fillRect(x - 100 / 2, y - 100 / 2, 100, 100);
});
相关的动图如下👇
Canvas 上操作贴纸
我们上面已经将贴纸拖动到 canvas 上了。那么,接下来,我们操作贴纸的功能,这里实现 移动、缩放和旋转 功能。
// 初始化参数
let isDragging = false;
let startX;
let startY;
let rect = {
x: 100,
y: 100,
width: 100,
height: 100,
angle: 0, // 旋转的角度
scale: 1, // 缩放大小
};
我们在移动的过程中,不断地绘制该红色的矩形:
function drawRect() {
// 绘制前清空画布
ctx.clearReact(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(rect.x + rect.width / 2, rect.y + rect.height / 2); // 移动
ctx.rotate(rect.angle); // 旋转
ctx.scale(rect.scale, rect.scale); // 缩放
ctx.fillStyle = "red";
ctx.fillRect(-rect.width / 2, -rect.height / 2, rect.width, rect.height);
ctx.restore();
}
我们监听鼠标的落下,移动和抬起,来实现贴纸的移动距离和方向:
// 获取鼠标的当前位置
function getMousePos(canvas, event) {
const rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
}
// 判断点是否在红色的矩形内
function isInsideRect(x, y) {
const halfWidth = rect.widht / 2;
const halfHeight = rect.height / 2;
const cos = Math.cos(rect.angle);
const sin = Maht.sin(rect.angle);
const dx = x - (rect.x + halfWidth);
const dy = y - (rect.y + halfHeight);
const rx = dx * cos + dy * sin;
const ry = dy * cos - dx * sin;
return Math.abs(rx) <= halfWidth * rect.scale && Math.abs(ry) <= halfHeight * rect.scale;
}
// 监听鼠标按下
canvas.addEventListener("mousedown", (e) => {
const mousePos = (canvas, e);
if (isInsideRect(mousePos.x, mousePos.y)) {
isDragging = true;
startX = mousePos.x - rect.x;
startY = mousePos.y - rect.y;
}
});
// 监听鼠标移动
canvas.addEventListener("mouseMove", (e) => {
if (isDragging) {
const mousePos = getMousePos(canvas, e);
rect.x = mousePos.x - startX;
rect.y = mousePos.y - startY;
// 重新绘制
drawRect();
}
});
// 监听鼠标抬起
canvas.addEventListener("mouseup", () => {
isDragging = false;
});
我们通过监听鼠标滚轮的滑动来实现贴纸的放大和缩小:
canvas.addEventListener("mousewheel", (e) => {
e.preventDefault();
const delta = e.deltaY > 0 ? 0.1 : -0.1;
rect.scale += delta;
if (rect.scale < 1) {
rect.scale = 1;
}
if (rect.scale > 2) {
rect.scale = 2;
}
// 重新绘制
drawRect();
});
然后为了方便演示,我们通过监听鼠标右键来实现矩形的旋转功能:
canvas.addEventListener("contextmenu", (e) => {
e.preventDefault();
rect.angle += Math.PI / 12; // 每次点击,则旋转 15 度
// 重新绘制
drawRect();
})
整体的一个 Gif 图演示如下 👇
Canvas 合成图片
在绘制完成之后,我们需要将 canvas 上的状态图给保存下来。那么,我们可以使用 toDataURL 来实现。
function mixFn() {
// 首先,我们将其绘制下来
// other ...
if (context) {
context.fillStyle = "#fff";
context.fillRect(0, 0, canvas.width, canvas.height);
// 绘制相关的贴纸和模版图
// other ...
}
const image = canvas.toDataURL("image/jpeg", 1.0).substring(23);
// 置空
context = null;
canvas = null;
// 将 image 保存在指定位置即可
}