【vue】 vue2 canvas实现在图片上选点,画区域并将 坐标传递给后端

简介: 【vue】 vue2 canvas实现在图片上选点,画区域并将 坐标传递给后端

效果

代码

vue文件

<template>
  <div>
    <div class="tool-box">
      <el-button
        size="mini"
        :type="isDrawing ? 'warning' : 'primary'"
        @click="startDraw"
        >绘制区域</el-button
      >

      <el-button
        size="mini"
        type="danger"
        :disabled="isDrawing"
        @click="clearAll"
        >全部清除</el-button
      >
      <el-button
        size="mini"
        type="success"
        :disabled="isDrawing"
        @click="savePoints"
        >保存</el-button
      >
    </div>
    <div class="canvas-wrap">
      <canvas id="imgCanvas" ref="canvaxbox"></canvas>
      <!--用来和鼠标进行交互操作的canvas-->
      <canvas
        id="drawCanvas"
        ref="canvas"
        :style="{ cursor: isDrawing ? 'crosshair' : 'default' }"
      >
      </canvas>
      <!--存储已生成的点线,避免被清空-->
      <canvas id="saveCanvas" ref="canvasSave"></canvas>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      imgUrl: "img/code/wechat-code.jpg",
      isDrawing: false, // 是否正在绘制
      ratio: 1,
      imgWidth: 3020,
      imgHeight: 1080,
      wrapWidth: 300,
      wrapHeight: 300,
      canvasWidth: 300,
      canvasHeight: 300,
      drawingPoints: [],
      drawedPoints: [],
      imgCanvas: null,
      imgCtx: null,
      drawCanvas: null,
      drawCtx: null,
      saveCanvas: null,
      saveCtx: null,
      submitData: [
        // {"polygon":{"x1":0,"y1":0,"x2":1920,"y2":0,"x3":1920,"y3":1080,"x4":0,"y4":1080}},
        {
          polygon: {
            x1: 700,
            y1: 273,
            x2: 975,
            y2: 278,
            x3: 1107,
            y3: 368,
            x4: 718,
            y4: 354,
          },
        },
        {
          polygon: {
            x1: 49,
            y1: 32,
            x2: 183,
            y2: 35,
            x3: 181,
            y3: 100,
            x4: 55,
            y4: 97,
          },
        },
        {
          polygon: {
            x1: 433,
            y1: 250,
            x2: 706,
            y2: 253,
            x3: 707,
            y3: 392,
            x4: 435,
            y4: 393,
          },
        },
        {
          polygon: {
            x1: 45,
            y1: 539,
            x2: 193,
            y2: 538,
            x3: 192,
            y3: 622,
            x4: 41,
            y4: 623,
            x5: 42,
            y5: 623,
          },
        },
      ],
      // [
      //   {"polygon": {"x1":700,"y1":273,"x2":975,"y2":278,"x3":1107,"y3":368,"x4":718,"y4":354}}
      // ]
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.initCanvas();
      this.getImage();
    });
    // setTimeout(() => {
    // }, 500);
  },
  methods: {
    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");
      // this.initImgCanvas()
    },
    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(); // 渲染原有数据
      };
    },
    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);
    },
    clearAll() {
      // 清空所有绘制区域
      this.saveCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      this.drawedPoints = [];
    },
    getImage() {
      // 这里请求接口 ...

      // this.imgUrl = "https://w.wallhaven.cc/full/6d/wallhaven-6d5k6x.jpg";
      // this.imgWidth = 1920;
      // this.imgHeight = 1080;
      this.imgUrl =
        "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI";
      this.imgWidth = 200;
      this.imgHeight = 300;
      this.imgUrl && this.initImgCanvas();
    },
    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]);
        }
      }
    },
    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();
      }
    },
    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);
    },
    drawSaveArea(points) {
      // console.log(points, "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();
    },

    savePoints() {
      // 将画布坐标数据转换成提交数据
      let objectPoints = [];
      // "object": [{"polygon": {"x1":700,"y1":273,"x2":975,"y2":278,"x3":1107,"y3":368,"x4":718,"y4":354} }]
      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);
    },
    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;
      });
    },
  },
};
</script>
<style lang="scss" scoped>
.tool-box {
  width: 60vw;
  height: 40px;
  padding: 5px 30px;
  margin: 20px auto 0;
  box-sizing: border-box;
  text-align: right;
}
.canvas-wrap {
  // width: 80vw;
  // height: 45vw;
  width: 60vw;
  height: 33.75vw;
  margin: 0px auto;
  background-color: #000; //#fff;
  border: 3px;
  border-color: #333;
  position: relative;
}
#imgCanvas,
#drawCanvas,
#saveCanvas {
  background: rgba(255, 0, 255, 0);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
#drawCanvas {
  z-index: 2;
}
</style>

目录
相关文章
|
1月前
|
JavaScript NoSQL Redis
Vue中实现修改邮箱、手机号等流程的大致过程、验证码由后端的redis生成验证(版本1.0)
这篇文章记录了在Vue中实现修改手机号和邮箱的大致流程,包括使用过滤器部分隐藏展示的手机号和邮箱,以及通过点击触发路由跳转的便捷方式。文章还描述了旧号码和新号码验证的界面实现,其中验证码由后端生成并通过弹窗展示给用户,未来可以接入真正的手机验证码接口。此外,还提供了修改邮箱的页面效果截图,并强调了学习是一个永无止境的过程。
Vue中实现修改邮箱、手机号等流程的大致过程、验证码由后端的redis生成验证(版本1.0)
|
1月前
|
JavaScript 前端开发
Vue中传递自定义参数到后端、后端获取数据(使用Map接收参数)
这篇文章讲述了如何在Vue中通过Axios二次封装传递自定义参数到后端,并展示了后端如何使用Map接收这些参数,以及如何避免参数转换错误和统一接口设计的方法。
|
20天前
|
前端开发 Java Spring
Spring与Angular/React/Vue:当后端大佬遇上前端三杰,会擦出怎样的火花?一场技术的盛宴,你准备好了吗?
【8月更文挑战第31天】Spring框架与Angular、React、Vue等前端框架的集成是现代Web应用开发的核心。通过RESTful API、WebSocket及GraphQL等方式,Spring能与前端框架高效互动,提供快速且功能丰富的应用。RESTful API简单有效,适用于基本数据交互;WebSocket支持实时通信,适合聊天应用和数据监控;GraphQL则提供更精确的数据查询能力。开发者可根据需求选择合适的集成方式,提升用户体验和应用功能。
52 0
|
2月前
|
开发框架 前端开发 JavaScript
ABP框架测试信息---Winform端、动态网站、Vue&Element管理后端等
ABP框架测试信息---Winform端、动态网站、Vue&Element管理后端等
|
2月前
|
存储 开发框架 前端开发
循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合
循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合
|
2月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理
循序渐进VUE+Element 前端应用开发(24)--- 修改密码的前端界面和ABP后端设置处理
|
2月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理
循序渐进VUE+Element 前端应用开发(4)--- 获取后端数据及产品信息页面的处理
|
1月前
|
JavaScript 前端开发 安全
VUE——APP,后端,前端三端AES加密解密
VUE——APP,后端,前端三端AES加密解密
43 0
|
2月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(30)--- ABP后端和Vue+Element前端结合的分页排序处理
循序渐进VUE+Element 前端应用开发(30)--- ABP后端和Vue+Element前端结合的分页排序处理
|
2月前
|
存储 开发框架 前端开发
循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示
循序渐进VUE+Element 前端应用开发(14)--- 根据ABP后端接口实现前端界面展示

热门文章

最新文章