福田网站制作报价,软件外包项目网站,做任务兼职赚钱的网站,免费公文写作网站简介#xff1a;在Web开发中#xff0c;有时候我们需要在图片上进行一些交互式操作#xff0c;比如绘制区域、标记等。这种场景下#xff0c;我们可以使用HTML5的canvas元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口#xff0c;可以通过 JavaScript 在网页…简介在Web开发中有时候我们需要在图片上进行一些交互式操作比如绘制区域、标记等。这种场景下我们可以使用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);// 绘制canvasthis.drawCanvas document.getElementById(drawCanvas);this.drawCtx this.drawCanvas.getContext(2d);// 保存绘制区域 saveCanvasthis.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代码和原生js代码直接点我头像私我获取全部代码。 创作不易感觉有用就一键三连感谢(●◡●)