ToB企服应用市场:ToB评测及商务社交产业平台
标题:
Canvas绘制图片和地区(前端使用Canvas绘制图片,并在图片上绘制地区)
[打印本页]
作者:
杀鸡焉用牛刀
时间:
2024-7-26 20:57
标题:
Canvas绘制图片和地区(前端使用Canvas绘制图片,并在图片上绘制地区)
简介:在开辟中,偶尔我们必要在图片上举行一些交互式操作,好比绘制地区、标志等。这种场景下,我们可以使用HTML5的<canvas>元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口,可以通过 JavaScript 在网页上绘制图形、动画和其他视觉效果。这里来记录一下,怎样在一张图片上,绘制地区。
那么详细怎样使用Canvas在图片上绘制地区呢?
一. 首先,我们必要初始化三个canvas画布(初始化Canvas)
initCanvas() {
// 初始化canvas画布
let canvasWrap = document.getElementsByClassName("canvas-wrap");
this.wrapWidth = canvasWrap[0].clientWidth;
this.wrapHeight = canvasWrap[0].clientHeight;
this.imgCanvas = document.getElementById("imgCanvas");
this.imgCtx = this.imgCanvas.getContext("2d");
// 绘制canvas
this.drawCanvas = document.getElementById("drawCanvas");
this.drawCtx = this.drawCanvas.getContext("2d");
// 保存绘制区域 saveCanvas
this.saveCanvas = document.getElementById("saveCanvas");
this.saveCtx = this.saveCanvas.getContext("2d");
},},
复制代码
imgCanvas用于绘制原始图片
drawCanvas用于临时绘制地区
saveCanvas用于保存终极绘制的地区
二. 盘算并设置canvas的宽高比例,以顺应图片尺寸
initImgCanvas() {
// 计算宽高比
let ww = this.wrapWidth; // 画布宽度
let wh = this.wrapHeight; // 画布高度
let iw = this.imgWidth; // 图片宽度
let ih = this.imgHeight; // 图片高度
if (iw / ih < ww / wh) {
// 以高为主
this.ratio = ih / wh;
this.canvasHeight = wh;
this.canvasWidth = (wh * iw) / ih;
} else {
// 以宽为主
this.ratio = iw / ww;
this.canvasWidth = ww;
this.canvasHeight = (ww * ih) / iw;
}
// 初始化画布大小
this.imgCanvas.width = this.canvasWidth;
this.imgCanvas.height = this.canvasHeight;
this.drawCanvas.width = this.canvasWidth;
this.drawCanvas.height = this.canvasHeight;
this.saveCanvas.width = this.canvasWidth;
this.saveCanvas.height = this.canvasHeight;
// 图片加载绘制
let img = document.createElement("img");
img.src = this.imgUrl;
img.onload = () => {
console.log("图片已加载");
this.imgCtx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight);
this.renderDatas(); // 渲染原有数据
};
},},
复制代码
这里先盘算画布和图片的宽高比,根据比例关系决定以宽为主还是以高为主举行等比缩放。然后设置三个canvas的宽高,并在图片加载完成后将其绘制到imgCanvas上。renderDatas函数用于渲染已有的绘制数据(如果有的话)。
三. 开始绘制,绘制的重要逻辑
startDraw() {
// 绘制区域
if (this.isDrawing) return;
this.isDrawing = true;
// 绘制逻辑
this.drawCanvas.addEventListener("click", this.drawImageClickFn);
this.drawCanvas.addEventListener("dblclick", this.drawImageDblClickFn);
this.drawCanvas.addEventListener("mousemove", this.drawImageMoveFn);
},},
复制代码
我们在drawCanvas上监听click、dblclick和mousemove事件,分别对应点击、双击和鼠标移动三种绘制交互。
四. 点击事件,用于开始一个新的地区绘制
drawImageClickFn(e) {
let drawCtx = this.drawCtx;
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];
if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
this.drawingPoints.push([pointX, pointY]);
}
}
},},
复制代码
这里获取鼠标点击的坐标,并将其推入drawingPoints数组中,用于临时保存当前绘制地区的点坐标。
五. 鼠标移动事件,用于及时绘制地区
drawImageMoveFn(e) {
let drawCtx = this.drawCtx;
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
// 绘制
drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 绘制点
drawCtx.fillStyle = "blue";
this.drawingPoints.forEach((item, i) => {
drawCtx.beginPath();
drawCtx.arc(...item, 6, 0, 180);
drawCtx.fill(); //填充
});
// 绘制动态区域
drawCtx.save();
drawCtx.beginPath();
this.drawingPoints.forEach((item, i) => {
drawCtx.lineTo(...item);
});
drawCtx.lineTo(pointX, pointY);
drawCtx.lineWidth = "3";
drawCtx.strokeStyle = "blue";
drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";
drawCtx.stroke();
drawCtx.fill(); //填充
drawCtx.restore();
}
},},
复制代码
这里先清空drawCanvas,然后遍历drawingPoints数组,绘制已经点击的点。接着再绘制一个动态地区,即从第一个点开始,连线到当前鼠标位置,形成一个闭合多边形地区。
六. 双击事件,用于完成当前地区的绘制
drawImageDblClickFn(e) {
let drawCtx = this.drawCtx;
let saveCtx = this.saveCtx;
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];
if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
this.drawingPoints.push([pointX, pointY]);
}
}
// 清空绘制图层
drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 绘制区域至保存图层
this.drawSaveArea(this.drawingPoints);
this.drawedPoints.push(this.drawingPoints);
this.drawingPoints = [];
this.isDrawing = false;
// 绘制结束逻辑
this.drawCanvas.removeEventListener("click", this.drawImageClickFn);
this.drawCanvas.removeEventListener("dblclick", this.drawImageDblClickFn);
this.drawCanvas.removeEventListener("mousemove", this.drawImageMoveFn);
},},
复制代码
双击时,先获取双击的坐标点,并将其推入drawingPoints数组中。然后清空drawCanvas,并调用drawSaveArea方法,将当前绘制地区渲染到saveCanvas上。
七. 遍历地区点坐标的方法
drawSaveArea(points) {
if (points.length === 0) return;
this.saveCtx.save();
this.saveCtx.beginPath();
points.forEach((item, i) => {
this.saveCtx.lineTo(...item);
});
this.saveCtx.closePath();
this.saveCtx.lineWidth = "2";
this.saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";
this.saveCtx.strokeStyle = "red";
this.saveCtx.stroke();
this.saveCtx.fill();
this.saveCtx.restore();
},},
复制代码
drawSaveArea方法会遍历当前地区的所有点坐标,并在saveCanvas上绘制一个闭合的多边形地区,边框为赤色,填充为半透明的紫色。接下来,将当前绘制地区的点坐标数组drawingPoints推入drawedPoints数组中,用于保存所有已绘制的地区数据。然后,重置drawingPoints和isDrawing的状态,并移除所有绘制事件的监听器。
至此,一个地区的绘制就完成了。如果必要继续绘制新的地区,只需再次调用startDraw方法即可。
八. 保存和渲染数据。我们必要将绘制的地区数据保存下来,以及从已有数据中处置惩罚出必要的地区数据
savePoints() {
// 将画布坐标数据转换成提交数据
let objectPoints = [];
objectPoints = this.drawedPoints.map((area) => {
let polygon = {};
area.forEach((point, i) => {
polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio);
polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio);
});
return {
polygon: polygon,
};
});
this.submitData = objectPoints;
console.log("最终提交数据", objectPoints);
},},
复制代码
这里遍历所有已绘制的地区drawedPoints,将每个地区的点坐标根据ratio举行缩放(实际图片尺寸),并转换成一个polygon对象的形式,终极保存在submitData中。
九. 渲染数据
renderDatas() {
// 将提交数据数据转换成画布坐标
this.drawedPoints = this.submitData.map((item) => {
let polygon = item.polygon;
let points = [];
for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
points.push([
polygon[`x${i}`] / this.ratio,
polygon[`y${i}`] / this.ratio,
]);
}
}
this.drawSaveArea(points);
return points;
});
},},
复制代码
渲染数据的逻辑是,遍历submitData中的每个polygon对象,根据ratio将其坐标值转换成canvas的坐标值,并调用drawSaveArea方法将其渲染到saveCanvas上。至此,我们就完成了在canvas上绘制图片地区的全部逻辑。可以根据详细需求举行相应的调整和扩展。
十. 执行过程
详细全部的执行顺序如下:
初始化Canvas
调用initCanvas()方法初始化三个Canvas画布
调用initImgCanvas()方法盘算并设置画布宽高比例,加载并绘制图片
开始绘制
调用startDraw()方法
监听drawCanvas的click、dblclick、mousemove事件
点击时,在drawImageClickFn中记录点坐标
移动时,在drawImageMoveFn中及时绘制地区
双击时,在drawImageDblClickFn中完成当前地区绘制,保存至saveCanvas
保存和渲染数据
调用savePoints()方法,将绘制地区的点坐标数据转换并保存到submitData中
调用renderDatas()方法,将submitData中的数据转换并渲染到saveCanvas上
简单来说,就是先初始化画布,然后开始绘制地区的交互,最后保存和渲染数据。
十一. 当然,如果想使用原生JS实现,可以改成像下面这样
let canvasWrap, wrapWidth, wrapHeight, imgCanvas, imgCtx, drawCanvas,
drawCtx, saveCanvas, saveCtx;
let ratio, canvasWidth, canvasHeight, imgWidth, imgHeight, imgUrl;
let isDrawing = false;
let drawingPoints = [];
let drawedPoints = [];
let submitData = [];
// 1. 初始化Canvas画布
function initCanvas() {
// 获取canvas容器元素并设置宽高
canvasWrap = document.getElementsByClassName("canvas-wrap")[0];
wrapWidth = canvasWrap.clientWidth;
wrapHeight = canvasWrap.clientHeight;
// 获取canvas元素并获取2D绘图上下文
imgCanvas = document.getElementById("imgCanvas");
imgCtx = imgCanvas.getContext("2d");
drawCanvas = document.getElementById("drawCanvas");
drawCtx = drawCanvas.getContext("2d");
saveCanvas = document.getElementById("saveCanvas");
saveCtx = saveCanvas.getContext("2d");
}
// 2. 初始化图片Canvas
function initImgCanvas() {
// 计算画布和图片的宽高比
let ww = wrapWidth;
let wh = wrapHeight;
let iw = imgWidth;
let ih = imgHeight;
if (iw / ih < ww / wh) {
ratio = ih / wh;
canvasHeight = wh;
canvasWidth = (wh * iw) / ih;
} else {
ratio = iw / ww;
canvasWidth = ww;
canvasHeight = (ww * ih) / iw;
}
// 设置三个canvas的宽高
imgCanvas.width = canvasWidth;
imgCanvas.height = canvasHeight;
drawCanvas.width = canvasWidth;
drawCanvas.height = canvasHeight;
saveCanvas.width = canvasWidth;
saveCanvas.height = canvasHeight;
// 加载图片并绘制到imgCanvas上
let img = document.createElement("img");
img.src = imgUrl;
img.onload = () => {
console.log("图片已加载");
imgCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
renderDatas(); // 渲染已有数据
};
}
// 3. 开始绘制
function startDraw() {
if (isDrawing) return;
isDrawing = true;
// 监听drawCanvas的click、dblclick和mousemove事件
drawCanvas.addEventListener("click", drawImageClickFn);
drawCanvas.addEventListener("dblclick", drawImageDblClickFn);
drawCanvas.addEventListener("mousemove", drawImageMoveFn);
}
// 4. 清空所有绘制区域
function clearAll() {
saveCtx.clearRect(0, 0, canvasWidth, canvasHeight);
drawedPoints = [];
}
// 5. 获取并加载图片
function getImage() {
imgUrl = "需要渲染的图片地址";
imgWidth = 200;
imgHeight = 300;
imgUrl && initImgCanvas();
}
// 6. 点击事件,记录点坐标
function drawImageClickFn(e) {
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
let lastPoint = drawingPoints[drawingPoints.length - 1] || [];
if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
drawingPoints.push([pointX, pointY]);
}
}
}
// 7. 鼠标移动事件,实时绘制区域
function drawImageMoveFn(e) {
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);
drawCtx.fillStyle = "blue";
drawingPoints.forEach((item, i) => {
drawCtx.beginPath();
drawCtx.arc(...item, 6, 0, 180);
drawCtx.fill();
});
drawCtx.save();
drawCtx.beginPath();
drawingPoints.forEach((item, i) => {
drawCtx.lineTo(...item);
});
drawCtx.lineTo(pointX, pointY);
drawCtx.lineWidth = "3";
drawCtx.strokeStyle = "blue";
drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";
drawCtx.stroke();
drawCtx.fill();
drawCtx.restore();
}
}
// 8. 双击事件,完成当前区域绘制
function drawImageDblClickFn(e) {
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
let lastPoint = drawingPoints[drawingPoints.length - 1] || [];
if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
drawingPoints.push([pointX, pointY]);
}
}
drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);
drawSaveArea(drawingPoints);
drawedPoints.push(drawingPoints);
drawingPoints = [];
isDrawing = false;
drawCanvas.removeEventListener("click", drawImageClickFn);
drawCanvas.removeEventListener("dblclick", drawImageDblClickFn);
drawCanvas.removeEventListener("mousemove", drawImageMoveFn);
}
// 9. 绘制区域到saveCanvas
function drawSaveArea(points) {
if (points.length === 0) return;
saveCtx.save();
saveCtx.beginPath();
points.forEach((item, i) => {
saveCtx.lineTo(...item);
});
saveCtx.closePath();
saveCtx.lineWidth = "2";
saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";
saveCtx.strokeStyle = "red";
saveCtx.stroke();
saveCtx.fill();
saveCtx.restore();
}
// 10. 保存绘制数据
function savePoints() {
let objectPoints = [];
objectPoints = drawedPoints.map((area) => {
let polygon = {};
area.forEach((point, i) => {
polygon[`x${i + 1}`] = Math.round(point[0] * ratio);
polygon[`y${i + 1}`] = Math.round(point[1] * ratio);
});
return {
polygon: polygon,
};
});
submitData = objectPoints;
console.log("最终提交数据", objectPoints);
}
// 11. 渲染已有数据
function renderDatas() {
drawedPoints = submitData.map((item) => {
let polygon = item.polygon;
let points = [];
for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
points.push([
polygon[`x${i}`] / ratio, // 根据ratio换算canvas坐标
polygon[`y${i}`] / ratio,
]);
}
}
drawSaveArea(points); // 调用drawSaveArea将区域绘制到saveCanvas上
return points;
});
},},
// 使用方式
initCanvas(); // 1. 初始化Canvas画布
getImage(); // 5. 获取并加载图片
startDraw(); // 3. 开始绘制
复制代码
详细流程:
renderDatas函数的作用是将已有的绘制数据(submitData)转换成canvas坐标,
并调用drawSaveArea方法将其渲染到saveCanvas上,
该函数遍历submitData中的每个polygon对象,
根据ratio将其坐标值转换成canvas的坐标值,
然后调用drawSaveArea方法绘制该地区,
终极返回一个包罗所有地区点坐标的数组drawedPoints,
最后,必要按顺序调用initCanvas() -> getImage() -> startDraw()等方法,分别完成初始化画布、加载图片和开始绘制的功能。
十二. 全部代码
全部的VueJS代码和原生JavaScript代码,请打赏后,直接点我头像,私我,获取全部代码。
创作不易,感觉有用,就一键三连,感谢(●'◡'●)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4