Canvas绘制图片和地区(前端使用Canvas绘制图片,并在图片上绘制地区) ...

打印 上一主题 下一主题

主题 546|帖子 546|积分 1638

简介:在开辟中,偶尔我们必要在图片上举行一些交互式操作,好比绘制地区、标志等。这种场景下,我们可以使用HTML5的<canvas>元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口,可以通过 JavaScript 在网页上绘制图形、动画和其他视觉效果。这里来记录一下,怎样在一张图片上,绘制地区。

 

 


 

那么详细怎样使用Canvas在图片上绘制地区呢?

一. 首先,我们必要初始化三个canvas画布(初始化Canvas)

  1. initCanvas() {
  2.   // 初始化canvas画布
  3.   let canvasWrap = document.getElementsByClassName("canvas-wrap");
  4.   this.wrapWidth = canvasWrap[0].clientWidth;
  5.   this.wrapHeight = canvasWrap[0].clientHeight;
  6.   this.imgCanvas = document.getElementById("imgCanvas");
  7.   this.imgCtx = this.imgCanvas.getContext("2d");
  8.   // 绘制canvas
  9.   this.drawCanvas = document.getElementById("drawCanvas");
  10.   this.drawCtx = this.drawCanvas.getContext("2d");
  11.   // 保存绘制区域 saveCanvas
  12.   this.saveCanvas = document.getElementById("saveCanvas");
  13.   this.saveCtx = this.saveCanvas.getContext("2d");
  14. },},
复制代码

  • imgCanvas用于绘制原始图片
  • drawCanvas用于临时绘制地区
  • saveCanvas用于保存终极绘制的地区
 

 

二. 盘算并设置canvas的宽高比例,以顺应图片尺寸

  1. initImgCanvas() {
  2.   // 计算宽高比
  3.   let ww = this.wrapWidth; // 画布宽度
  4.   let wh = this.wrapHeight; // 画布高度
  5.   let iw = this.imgWidth; // 图片宽度
  6.   let ih = this.imgHeight; // 图片高度
  7.   if (iw / ih < ww / wh) {
  8.     // 以高为主
  9.     this.ratio = ih / wh;
  10.     this.canvasHeight = wh;
  11.     this.canvasWidth = (wh * iw) / ih;
  12.   } else {
  13.     // 以宽为主
  14.     this.ratio = iw / ww;
  15.     this.canvasWidth = ww;
  16.     this.canvasHeight = (ww * ih) / iw;
  17.   }
  18.   // 初始化画布大小
  19.   this.imgCanvas.width = this.canvasWidth;
  20.   this.imgCanvas.height = this.canvasHeight;
  21.   this.drawCanvas.width = this.canvasWidth;
  22.   this.drawCanvas.height = this.canvasHeight;
  23.   this.saveCanvas.width = this.canvasWidth;
  24.   this.saveCanvas.height = this.canvasHeight;
  25.   // 图片加载绘制
  26.   let img = document.createElement("img");
  27.   img.src = this.imgUrl;
  28.   img.onload = () => {
  29.     console.log("图片已加载");
  30.     this.imgCtx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight);
  31.     this.renderDatas(); // 渲染原有数据
  32.   };
  33. },},
复制代码
这里先盘算画布和图片的宽高比,根据比例关系决定以宽为主还是以高为主举行等比缩放。然后设置三个canvas的宽高,并在图片加载完成后将其绘制到imgCanvas上。renderDatas函数用于渲染已有的绘制数据(如果有的话)。
 

 

三. 开始绘制,绘制的重要逻辑

  1. startDraw() {
  2.   // 绘制区域
  3.   if (this.isDrawing) return;
  4.   this.isDrawing = true;
  5.   // 绘制逻辑
  6.   this.drawCanvas.addEventListener("click", this.drawImageClickFn);
  7.   this.drawCanvas.addEventListener("dblclick", this.drawImageDblClickFn);
  8.   this.drawCanvas.addEventListener("mousemove", this.drawImageMoveFn);
  9. },},
复制代码
我们在drawCanvas上监听click、dblclick和mousemove事件,分别对应点击、双击和鼠标移动三种绘制交互。
 

 
四. 点击事件,用于开始一个新的地区绘制

  1. drawImageClickFn(e) {
  2.   let drawCtx = this.drawCtx;
  3.   if (e.offsetX || e.layerX) {
  4.     let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
  5.     let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
  6.     let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];
  7.     if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
  8.       this.drawingPoints.push([pointX, pointY]);
  9.     }
  10.   }
  11. },},
复制代码
这里获取鼠标点击的坐标,并将其推入drawingPoints数组中,用于临时保存当前绘制地区的点坐标。
 

 
五. 鼠标移动事件,用于及时绘制地区

  1. drawImageMoveFn(e) {
  2.   let drawCtx = this.drawCtx;
  3.   if (e.offsetX || e.layerX) {
  4.     let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
  5.     let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
  6.     // 绘制
  7.     drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
  8.     // 绘制点
  9.     drawCtx.fillStyle = "blue";
  10.     this.drawingPoints.forEach((item, i) => {
  11.       drawCtx.beginPath();
  12.       drawCtx.arc(...item, 6, 0, 180);
  13.       drawCtx.fill(); //填充
  14.     });
  15.     // 绘制动态区域
  16.     drawCtx.save();
  17.     drawCtx.beginPath();
  18.     this.drawingPoints.forEach((item, i) => {
  19.       drawCtx.lineTo(...item);
  20.     });
  21.     drawCtx.lineTo(pointX, pointY);
  22.     drawCtx.lineWidth = "3";
  23.     drawCtx.strokeStyle = "blue";
  24.     drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";
  25.     drawCtx.stroke();
  26.     drawCtx.fill(); //填充
  27.     drawCtx.restore();
  28.   }
  29. },},
复制代码
这里先清空drawCanvas,然后遍历drawingPoints数组,绘制已经点击的点。接着再绘制一个动态地区,即从第一个点开始,连线到当前鼠标位置,形成一个闭合多边形地区。
 

 
六. 双击事件,用于完成当前地区的绘制

  1. drawImageDblClickFn(e) {
  2.   let drawCtx = this.drawCtx;
  3.   let saveCtx = this.saveCtx;
  4.   if (e.offsetX || e.layerX) {
  5.     let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
  6.     let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
  7.     let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];
  8.     if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
  9.       this.drawingPoints.push([pointX, pointY]);
  10.     }
  11.   }
  12.   // 清空绘制图层
  13.   drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
  14.   // 绘制区域至保存图层
  15.   this.drawSaveArea(this.drawingPoints);
  16.   this.drawedPoints.push(this.drawingPoints);
  17.   this.drawingPoints = [];
  18.   this.isDrawing = false;
  19.   // 绘制结束逻辑
  20.   this.drawCanvas.removeEventListener("click", this.drawImageClickFn);
  21.   this.drawCanvas.removeEventListener("dblclick", this.drawImageDblClickFn);
  22.   this.drawCanvas.removeEventListener("mousemove", this.drawImageMoveFn);
  23. },},
复制代码
双击时,先获取双击的坐标点,并将其推入drawingPoints数组中。然后清空drawCanvas,并调用drawSaveArea方法,将当前绘制地区渲染到saveCanvas上。
 

 
七. 遍历地区点坐标的方法

  1. drawSaveArea(points) {
  2.   if (points.length === 0) return;
  3.   this.saveCtx.save();
  4.   this.saveCtx.beginPath();
  5.   points.forEach((item, i) => {
  6.     this.saveCtx.lineTo(...item);
  7.   });
  8.   this.saveCtx.closePath();
  9.   this.saveCtx.lineWidth = "2";
  10.   this.saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";
  11.   this.saveCtx.strokeStyle = "red";
  12.   this.saveCtx.stroke();
  13.   this.saveCtx.fill();
  14.   this.saveCtx.restore();
  15. },},
复制代码
drawSaveArea方法会遍历当前地区的所有点坐标,并在saveCanvas上绘制一个闭合的多边形地区,边框为赤色,填充为半透明的紫色。接下来,将当前绘制地区的点坐标数组drawingPoints推入drawedPoints数组中,用于保存所有已绘制的地区数据。然后,重置drawingPoints和isDrawing的状态,并移除所有绘制事件的监听器。
至此,一个地区的绘制就完成了。如果必要继续绘制新的地区,只需再次调用startDraw方法即可。
 

 
八. 保存和渲染数据。我们必要将绘制的地区数据保存下来,以及从已有数据中处置惩罚出必要的地区数据

  1. savePoints() {
  2.   // 将画布坐标数据转换成提交数据
  3.   let objectPoints = [];
  4.   objectPoints = this.drawedPoints.map((area) => {
  5.     let polygon = {};
  6.     area.forEach((point, i) => {
  7.       polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio);
  8.       polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio);
  9.     });
  10.     return {
  11.       polygon: polygon,
  12.     };
  13.   });
  14.   this.submitData = objectPoints;
  15.   console.log("最终提交数据", objectPoints);
  16. },},
复制代码
这里遍历所有已绘制的地区drawedPoints,将每个地区的点坐标根据ratio举行缩放(实际图片尺寸),并转换成一个polygon对象的形式,终极保存在submitData中。
 

 
九. 渲染数据

  1. renderDatas() {
  2.   // 将提交数据数据转换成画布坐标
  3.   this.drawedPoints = this.submitData.map((item) => {
  4.     let polygon = item.polygon;
  5.     let points = [];
  6.     for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
  7.       if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
  8.         points.push([
  9.           polygon[`x${i}`] / this.ratio,
  10.           polygon[`y${i}`] / this.ratio,
  11.         ]);
  12.       }
  13.     }
  14.     this.drawSaveArea(points);
  15.     return points;
  16.   });
  17. },},
复制代码
渲染数据的逻辑是,遍历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实现,可以改成像下面这样

  
  1. let canvasWrap, wrapWidth, wrapHeight, imgCanvas, imgCtx, drawCanvas,
  2. drawCtx, saveCanvas, saveCtx;
  3. let ratio, canvasWidth, canvasHeight, imgWidth, imgHeight, imgUrl;
  4. let isDrawing = false;
  5. let drawingPoints = [];
  6. let drawedPoints = [];
  7. let submitData = [];
  8. // 1. 初始化Canvas画布
  9. function initCanvas() {
  10.   // 获取canvas容器元素并设置宽高
  11.   canvasWrap = document.getElementsByClassName("canvas-wrap")[0];
  12.   wrapWidth = canvasWrap.clientWidth;
  13.   wrapHeight = canvasWrap.clientHeight;
  14.   // 获取canvas元素并获取2D绘图上下文
  15.   imgCanvas = document.getElementById("imgCanvas");
  16.   imgCtx = imgCanvas.getContext("2d");
  17.   drawCanvas = document.getElementById("drawCanvas");
  18.   drawCtx = drawCanvas.getContext("2d");
  19.   saveCanvas = document.getElementById("saveCanvas");
  20.   saveCtx = saveCanvas.getContext("2d");
  21. }
  22. // 2. 初始化图片Canvas
  23. function initImgCanvas() {
  24.   // 计算画布和图片的宽高比
  25.   let ww = wrapWidth;
  26.   let wh = wrapHeight;
  27.   let iw = imgWidth;
  28.   let ih = imgHeight;
  29.   if (iw / ih < ww / wh) {
  30.     ratio = ih / wh;
  31.     canvasHeight = wh;
  32.     canvasWidth = (wh * iw) / ih;
  33.   } else {
  34.     ratio = iw / ww;
  35.     canvasWidth = ww;
  36.     canvasHeight = (ww * ih) / iw;
  37.   }
  38.   // 设置三个canvas的宽高
  39.   imgCanvas.width = canvasWidth;
  40.   imgCanvas.height = canvasHeight;
  41.   drawCanvas.width = canvasWidth;
  42.   drawCanvas.height = canvasHeight;
  43.   saveCanvas.width = canvasWidth;
  44.   saveCanvas.height = canvasHeight;
  45.   // 加载图片并绘制到imgCanvas上
  46.   let img = document.createElement("img");
  47.   img.src = imgUrl;
  48.   img.onload = () => {
  49.     console.log("图片已加载");
  50.     imgCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
  51.     renderDatas(); // 渲染已有数据
  52.   };
  53. }
  54. // 3. 开始绘制
  55. function startDraw() {
  56.   if (isDrawing) return;
  57.   isDrawing = true;
  58.   // 监听drawCanvas的click、dblclick和mousemove事件
  59.   drawCanvas.addEventListener("click", drawImageClickFn);
  60.   drawCanvas.addEventListener("dblclick", drawImageDblClickFn);
  61.   drawCanvas.addEventListener("mousemove", drawImageMoveFn);
  62. }
  63. // 4. 清空所有绘制区域
  64. function clearAll() {
  65.   saveCtx.clearRect(0, 0, canvasWidth, canvasHeight);
  66.   drawedPoints = [];
  67. }
  68. // 5. 获取并加载图片
  69. function getImage() {
  70.   imgUrl = "需要渲染的图片地址";
  71.   imgWidth = 200;
  72.   imgHeight = 300;
  73.   imgUrl && initImgCanvas();
  74. }
  75. // 6. 点击事件,记录点坐标
  76. function drawImageClickFn(e) {
  77.   if (e.offsetX || e.layerX) {
  78.     let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
  79.     let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
  80.     let lastPoint = drawingPoints[drawingPoints.length - 1] || [];
  81.     if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
  82.       drawingPoints.push([pointX, pointY]);
  83.     }
  84.   }
  85. }
  86. // 7. 鼠标移动事件,实时绘制区域
  87. function drawImageMoveFn(e) {
  88.   if (e.offsetX || e.layerX) {
  89.     let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
  90.     let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
  91.     drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);
  92.     drawCtx.fillStyle = "blue";
  93.     drawingPoints.forEach((item, i) => {
  94.       drawCtx.beginPath();
  95.       drawCtx.arc(...item, 6, 0, 180);
  96.       drawCtx.fill();
  97.     });
  98.     drawCtx.save();
  99.     drawCtx.beginPath();
  100.     drawingPoints.forEach((item, i) => {
  101.       drawCtx.lineTo(...item);
  102.     });
  103.     drawCtx.lineTo(pointX, pointY);
  104.     drawCtx.lineWidth = "3";
  105.     drawCtx.strokeStyle = "blue";
  106.     drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";
  107.     drawCtx.stroke();
  108.     drawCtx.fill();
  109.     drawCtx.restore();
  110.   }
  111. }
  112. // 8. 双击事件,完成当前区域绘制
  113. function drawImageDblClickFn(e) {
  114.   if (e.offsetX || e.layerX) {
  115.     let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
  116.     let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
  117.     let lastPoint = drawingPoints[drawingPoints.length - 1] || [];
  118.     if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
  119.       drawingPoints.push([pointX, pointY]);
  120.     }
  121.   }
  122.   drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);
  123.   drawSaveArea(drawingPoints);
  124.   drawedPoints.push(drawingPoints);
  125.   drawingPoints = [];
  126.   isDrawing = false;
  127.   drawCanvas.removeEventListener("click", drawImageClickFn);
  128.   drawCanvas.removeEventListener("dblclick", drawImageDblClickFn);
  129.   drawCanvas.removeEventListener("mousemove", drawImageMoveFn);
  130. }
  131. // 9. 绘制区域到saveCanvas
  132. function drawSaveArea(points) {
  133.   if (points.length === 0) return;
  134.   saveCtx.save();
  135.   saveCtx.beginPath();
  136.   points.forEach((item, i) => {
  137.     saveCtx.lineTo(...item);
  138.   });
  139.   saveCtx.closePath();
  140.   saveCtx.lineWidth = "2";
  141.   saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";
  142.   saveCtx.strokeStyle = "red";
  143.   saveCtx.stroke();
  144.   saveCtx.fill();
  145.   saveCtx.restore();
  146. }
  147. // 10. 保存绘制数据
  148. function savePoints() {
  149.   let objectPoints = [];
  150.   objectPoints = drawedPoints.map((area) => {
  151.     let polygon = {};
  152.     area.forEach((point, i) => {
  153.       polygon[`x${i + 1}`] = Math.round(point[0] * ratio);
  154.       polygon[`y${i + 1}`] = Math.round(point[1] * ratio);
  155.     });
  156.     return {
  157.       polygon: polygon,
  158.     };
  159.   });
  160.   submitData = objectPoints;
  161.   console.log("最终提交数据", objectPoints);
  162. }
  163. // 11. 渲染已有数据
  164. function renderDatas() {
  165.   drawedPoints = submitData.map((item) => {
  166.     let polygon = item.polygon;
  167.     let points = [];
  168.     for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
  169.       if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
  170.         points.push([
  171.           polygon[`x${i}`] / ratio, // 根据ratio换算canvas坐标
  172.           polygon[`y${i}`] / ratio,
  173.         ]);
  174.       }
  175.     }
  176.     drawSaveArea(points); // 调用drawSaveArea将区域绘制到saveCanvas上
  177.     return points;
  178.   });
  179. },},
  180. // 使用方式
  181. initCanvas(); // 1. 初始化Canvas画布
  182. getImage(); // 5. 获取并加载图片
  183. startDraw(); // 3. 开始绘制
复制代码
  详细流程:
   

  • renderDatas函数的作用是将已有的绘制数据(submitData)转换成canvas坐标,
  • 并调用drawSaveArea方法将其渲染到saveCanvas上,
  • 该函数遍历submitData中的每个polygon对象,
  • 根据ratio将其坐标值转换成canvas的坐标值,
  • 然后调用drawSaveArea方法绘制该地区,
  • 终极返回一个包罗所有地区点坐标的数组drawedPoints,
  • 最后,必要按顺序调用initCanvas() -> getImage() -> startDraw()等方法,分别完成初始化画布、加载图片和开始绘制的功能。
    
   十二. 全部代码

   全部的VueJS代码和原生JavaScript代码,请打赏后,直接点我头像,私我,获取全部代码。
 
    
    

    
         创作不易,感觉有用,就一键三连,感谢(●'◡'●)

     
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

杀鸡焉用牛刀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表