赞
踩
<canvas>
元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口,可以通过 JavaScript 在网页上绘制图形、动画和其他视觉效果。这里来记录一下,如何在一张图片上,绘制区域。- 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
用于保存最终绘制的区域- 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上绘制图片区域的全部逻辑。可以根据具体需求进行相应的调整和扩展。
initCanvas()
方法初始化三个Canvas画布initImgCanvas()
方法计算并设置画布宽高比例,加载并绘制图片startDraw()
方法drawCanvas
的click
、dblclick
、mousemove
事件drawImageClickFn
中记录点坐标drawImageMoveFn
中实时绘制区域drawImageDblClickFn
中完成当前区域绘制,保存至saveCanvas
savePoints()
方法,将绘制区域的点坐标数据转换并保存到submitData
中renderDatas()
方法,将submitData
中的数据转换并渲染到saveCanvas
上- 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代码和原生js代码直接点我头像,私我,获取全部代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。