效果預(yù)覽:
?? 項(xiàng)目地址?
實(shí)現(xiàn)[Vue3+TypeScript]
1.創(chuàng)建畫布并初始化
<canvas id="canvas" width="500" height="500"></canvas>
初始化畫布:
const canvas = ref<HTMLCanvasElement>();
let ctx: CanvasRenderingContext2D;
const width = 500; // 畫布的固定寬度
const height = 500; // 畫布的固定高度
canvas.value = document.getElementById("canvas") as HTMLCanvasElement;
ctx = canvas.value!.getContext("2d") as CanvasRenderingContext2D;
2.導(dǎo)入貓貓頭圖片愉豺,存儲(chǔ)為數(shù)組
const imgs = [
{ name: "p1", url: "/img/cat1.png" },
{ name: "p2", url: "/img/cat2.png" },
{ name: "p3", url: "/img/cat3.png" },
];
創(chuàng)建新數(shù)組imagesData用來(lái)存儲(chǔ)貓貓頭數(shù)據(jù)和圖片在畫布中的位置信息:
const imagesData: {
img: HTMLImageElement; // 圖片信息
name: string; // 圖片名稱
x: number; // x方向位置
y: number; // y方向位置
w: number; // 繪制寬度
h: number; // 繪制高度
}[] = [];
3.將貓貓頭隨機(jī)繪制在畫布上
imgs.forEach(item => {
let img = new Image();
img.src = item.url;
const name = item.name;
img.onload = function () {
// 這里取固定寬度100
const w = 100;
const h = (w / img.width) * img.height;
const x = Math.random() * (width - w);
const y = Math.random() * (height - h);
const obj = { img, name, x, y, w, h };
imagesData.push(obj);
drawByObj(obj);
};
});
function drawByObj(obj) {
ctx.drawImage(obj.img, obj.x, obj.y, obj.w, obj.h);
}
4.貓貓頭之點(diǎn)擊
首先要進(jìn)行判斷协饲,在鼠標(biāo)點(diǎn)下去時(shí)的目標(biāo)是否為我們繪制的圖片,這里可以使用isPointInPath方法圃酵。
isPointInPath:
是 Canvas 2D API 用于檢測(cè)某點(diǎn)是否在路徑的描邊線上的方法悦施。返回值是一個(gè)布爾值虐呻,當(dāng)這個(gè)點(diǎn)在路徑的描邊線上,則返回true琼腔,否則返回false瑰枫。使用方法:context.isPointInPath(x,y)。其中x,y分別是給定點(diǎn)的橫展姐、縱坐標(biāo)躁垛。
注意:該方法是針對(duì)路徑的,比如canvas中的rect圾笨、arc方法都可以用教馆,但是fillRect不可以用,因?yàn)樗皇锹窂嚼薮铮欢覂H對(duì)當(dāng)前的路徑有效土铺,而且如果當(dāng)前路徑有多個(gè)子路徑,則只對(duì)第一個(gè)子路徑有效板鬓。
給畫布添加鼠標(biāo)按下事件mousedown悲敷,記錄當(dāng)前點(diǎn)擊的位置,對(duì)該位置進(jìn)行判斷俭令,若該位置是貓貓頭后德,記錄下這張圖片的在imagesData中的位置target:
let clickCoordinate = { x: 0, y: 0 };
let target;
canvas.value.addEventListener("mousedown", mousedownFn, false);
function mousedownFn(e: MouseEvent) {
clickCoordinate.x = e.pageX - canvas.value!.offsetLeft;
clickCoordinate.y = e.pageY - canvas.value!.offsetTop;
console.log("初次點(diǎn)擊位置:" + clickCoordinate.x + "--" + clickCoordinate.y);
checkElement();
if (target == undefined) return;
canvas.value!.addEventListener("mousemove", mousemoveFn, false);
canvas.value!.addEventListener("mouseup", mouseupFn, false);
}
/** 判斷點(diǎn)擊的是哪個(gè) */
function checkElement() {
imagesData.forEach((item, index) => {
// 繪制輔助路徑
drawGuideByObj(item);
if (ctx.isPointInPath(clickCoordinate.x, clickCoordinate.y)) {
console.log("點(diǎn)擊的元素是:", item.name);
target = index;
}
});
}
// 根據(jù)貓貓頭的寬度和高度繪制輔助路徑,獲取正確的ctx抄腔,才能進(jìn)行判斷
function drawGuideByObj(obj: any) {
ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = "green";
ctx.rect(obj.x, obj.y, obj.w, obj.h);
ctx.stroke();
}
5.貓貓頭之拖拽
結(jié)果判斷后瓢湃,如果target的值存在理张,那么可以對(duì)貓貓頭進(jìn)行拖拽,這時(shí)需要給畫布添加鼠標(biāo)移動(dòng)事件mousemove和鼠標(biāo)抬起mouseup事件绵患。
function mousedownFn(e: MouseEvent) {
clickCoordinate.x = e.pageX - canvas.value!.offsetLeft;
clickCoordinate.y = e.pageY - canvas.value!.offsetTop;
console.log("初次點(diǎn)擊位置:" + clickCoordinate.x + "--" + clickCoordinate.y);
checkElement();
if (target == undefined) return;
canvas.value!.addEventListener("mousemove", mousemoveFn, false);
canvas.value!.addEventListener("mouseup", mouseupFn, false);
}
mousemove觸發(fā)事件:根據(jù)MouseEvent的值雾叭,更新貓貓頭的最新位置,并清空畫布重新繪制:
function mousemoveFn(e: MouseEvent) {
// 計(jì)算點(diǎn)擊位置和圖片原點(diǎn)位置的差
let sx = clickCoordinate.x - imagesData[target].x;
let sy = clickCoordinate.y - imagesData[target].y;
// 計(jì)算移動(dòng)元素的坐標(biāo)
imagesData[target].x = e.pageX - canvas.value!.offsetLeft - sx;
imagesData[target].y = e.pageY - canvas.value!.offsetTop - sy;
// 重新賦值點(diǎn)擊位置
clickCoordinate.x = e.pageX - canvas.value!.offsetLeft;
clickCoordinate.y = e.pageY - canvas.value!.offsetTop;
// 清空畫布
ctx.clearRect(0, 0, width, height);
// 清空畫布以后重新繪制
imagesData.forEach(i => drawByObj(i));
}
mouseup觸發(fā)事件:鼠標(biāo)抬起后落蝙,移除畫布相關(guān)事件:
function mouseupFn(e: MouseEvent) {
console.log(e.pageX + "----" + e.pageY);
// 鼠標(biāo)抬起以后移除事件
canvas.value!.removeEventListener("mousemove", mousemoveFn, false);
canvas.value!.removeEventListener("mouseup", mouseupFn, false);
target = undefined;
}
6.貓貓頭之下載
新增下載觸發(fā)事件:
<div class="btn" @click="save">保存</div>
將canvas存儲(chǔ)為本地圖片有3步:
- 將canvas轉(zhuǎn)換為base64的url:toDataURL
- 將base64轉(zhuǎn)換為文件對(duì)象
- 通過(guò)虛擬點(diǎn)擊下載文件
代碼如下:
const save = () => {
// 將canvas轉(zhuǎn)換成base64的url
let url = canvas.value!.toDataURL("image/png");
imgOnPage.value!.src = url;
// 將base64轉(zhuǎn)換為文件對(duì)象
let arr = url.split(",");
let mime = arr[0].match(/:(.*?);/)![1];
let binaryString = window.atob(arr[1]);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
let file = new File([bytes], "filename", { type: mime });
// 虛擬點(diǎn)擊
const link = document.createElement("a");
link.download = file.name;
let href = URL.createObjectURL(file);
link.href = href;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(href);
};