iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >vue下如何利用canvas实现在线图片标注
  • 616
分享到

vue下如何利用canvas实现在线图片标注

2024-04-02 19:04:59 616人浏览 独家记忆
摘要

目录组件代码如下在开发过程中遇到的问题WEB端实现在线图片标注在此做下记录,功能类似微信截图时的标注,包含画线、框、箭头和文字输入,思路是利用canvas画布,先把要标注的图片使用d

WEB端实现在线图片标注在此做下记录,功能类似微信截图时的标注,包含画线、框、箭头和文字输入,思路是利用canvas画布,先把要标注的图片使用drawImage方法画在画布上,然后定义画线、框、箭头和文字输入的方法调用

组件代码如下

<template>
  <div class="draw">
    <div class="drawTop" ref="drawTop" v-if="lineStep == lineNum">
      <div>
        <el-button type @click="resetAll">清空</el-button>
        <el-button type @click="repeal">撤销</el-button>
        <el-button type @click="canvasRedo">恢复</el-button>
        <el-button type @click="downLoad">下载</el-button>
      </div>
      <div style="width:22%">
        选择绘制类型:
        <el-radio-group v-model="type" size="medium">
          <el-radio-button
            v-for="(item,index) in typeOption"
            :key="index"
            :label="item.value"
            @click.native="radioclick(item.value)"
          >{{item.label}}
          </el-radio-button>
        </el-radio-group>
      </div>
      <div style="width:15%">
        边框粗细:
        <el-slider v-model="lineWidth" :min="0" :max="10" :step="1" style="width:70%"></el-slider>
      </div>
      <div>
        线条颜色:
        <el-color-picker v-model="strokeStyle"></el-color-picker>
      </div>
      <div>
        文字颜色:
        <el-color-picker v-model="fontColor"></el-color-picker>
      </div>
      <div style="width:15%">
        文字大小:
        <el-slider v-model="fontSize" :min="14" :max="36" :step="2" style="width:70%"></el-slider>
      </div>
    </div>
    <div style="height: 100%;width: 100%;position:relative;">
      <div class="content"></div>
      <input v-show="isshow" type="text" @blur="txtBlue" ref="txt" id="txt"
             style="z-index: 9999;position: absolute;border: 0;background:none;outline: none;"/>
    </div>
  </div>
</template>
<script>
  export default {
    name: "callout",
    props: {
      imgPath: undefined,
 
    },
    data() {
      return {
        isShow: false,
        canvas: "",
        ctx: "",
        ctxX: 0,
        ctxY: 0,
        lineWidth: 1,
        type: "L",
        typeOption: [
          {label: "线", value: "L"},
          {label: "矩形", value: "R"},
          {label: "箭头", value: "A"},
          {label: "文字", value: "T"},
        ],
        canvasHistory: [],
        step: 0,
        loading: false,
        fillStyle: "#CB0707",
        strokeStyle: "#CB0707",
        lineNum: 2,
        linePeak: [],
        lineStep: 2,
        ellipseR: 0.5,
        dialogVisible: false,
        isUnfold: true,
        fontSize: 24,
        fontColor: "#CB0707",
        fontFamily: '微软雅黑',
        img: new Image(),
      };
    },
    mounted() {
      let _this = this;
      let image = new Image();
      image.setAttribute('crossOrigin', 'anonymous');
      image.src = this.imgPath;
      image.onload = function () {//图片加载完,再draw 和 toDataURL
        if (image.complete) {
          _this.img = image
          let content = document.getElementsByClassName("content")[0];
          _this.canvas = document.createElement("canvas");
          _this.canvas.height = _this.img.height
          _this.canvas.width = _this.img.width
          _this.ctx = _this.canvas.getContext("2d");
          _this.ctx.globalAlpha = 1;
          _this.ctx.drawImage(_this.img, 0, 0)
          _this.canvasHistory.push(_this.canvas.toDataURL());
          _this.ctx.globalCompositeOperation = _this.type;
          content.appendChild(_this.canvas);
          _this.bindEventLisner();
        }
      }
    },
    methods: {
      radioClick(item) {
        if (item != "T") {
          this.txtBlue()
          this.resetTxt()
        }
      },
      // 下载画布
      downLoad() {
        let _this = this;
        let url = _this.canvas.toDataURL("image/png");
        let fileName = "canvas.png";
        if ("download" in document.createElement("a")) {
          // 非IE下载
          const elink = document.createElement("a");
          elink.download = fileName;
          elink.style.display = "none";
          elink.href = url;
          document.body.appendChild(elink);
          elink.click();
          document.body.removeChild(elink);
        } else {
          // IE10+下载
          navigator.msSaveBlob(url, fileName);
        }
      },
      // 清空画布及历史记录
      resetAll() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.canvasHistory = [];
        this.ctx.drawImage(this.img, 0, 0);
        this.canvasHistory.push(this.canvas.toDataURL());
        this.step = 0;
        this.resetTxt();
      },
      // 清空当前画布
      reset() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.drawImage(this.img, 0, 0);
        this.resetTxt();
      },
      // 撤销方法
      repeal() {
        let _this = this;
        if (this.isShow) {
          _this.resetTxt();
          _this._repeal();
        } else {
          _this._repeal();
        }
 
      },
      _repeal() {
        if (this.step >= 1) {
          this.step = this.step - 1;
          let canvasPic = new Image();
          canvasPic.src = this.canvasHistory[this.step];
          canvasPic.addEventListener("load", () => {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.ctx.drawImage(canvasPic, 0, 0);
            this.loading = true;
          });
        } else {
          this.$message.warning("不能再继续撤销了");
        }
      },
      // 恢复方法
      canvasRedo() {
        if (this.step < this.canvasHistory.length - 1) {
          if (this.step == 0) {
            this.step = 1;
          } else {
            this.step++;
          }
          let canvasPic = new Image();
          canvasPic.src = this.canvasHistory[this.step];
          canvasPic.addEventListener("load", () => {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.ctx.drawImage(canvasPic, 0, 0);
          });
        } else {
          this.$message.warning("已经是最新的记录了");
        }
      },
      // 绘制历史数组中的最后一个
      rebroadcast() {
        let canvasPic = new Image();
        canvasPic.src = this.canvasHistory[this.step];
        canvasPic.addEventListener("load", () => {
          this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
          this.ctx.drawImage(canvasPic, 0, 0);
          this.loading = true;
        });
      },
      // 绑定事件,判断分支
      bindEventLisner() {
        let _this = this;
        let r1, r2; // 绘制圆形,矩形需要
        this.canvas.onmousedown = function (e) {
          console.log("onmousedown");
          if (_this.type == "L") {
            _this.createL(e, "begin");
          } else if (_this.type == "R") {
            r1 = e.layerX;
            r2 = e.layerY;
            _this.createR(e, "begin", r1, r2);
          } else if (_this.type == "A") {
            _this.drawArrow(e, "begin")
          } else if (_this.type == "T") {
            _this.createT(e, "begin")
          }
        };
        this.canvas.onmouseup = function (e) {
          console.log("onmouseup");
          if (_this.type == "L") {
            _this.createL(e, "end");
          } else if (_this.type == "R") {
            _this.createR(e, "end", r1, r2);
            r1 = null;
            r2 = null;
          } else if (_this.type == "A") {
            _this.drawArrow(e, "end")
          } else if (_this.type == "T") {
            _this.createT(e, "end")
          }
        };
 
      },
      // 绘制线条
      createL(e, status) {
        let _this = this;
        if (status == "begin") {
          _this.ctx.beginPath();
          _this.ctx.moveTo(e.layerX, e.layerY);
          _this.canvas.onmousemove = function (e) {
            console.log("onmousemove");
            _this.ctx.lineTo(e.layerX, e.layerY);
            _this.ctx.strokeStyle = _this.strokeStyle;
            _this.ctx.lineWidth = _this.lineWidth;
            _this.ctx.stroke();
          };
        } else if (status == "end") {
          _this.ctx.closePath();
          _this.step = _this.step + 1;
          if (_this.step < _this.canvasHistory.length - 1) {
            _this.canvasHistory.length = _this.step; // 截断数组
          }
          _this.canvasHistory.push(_this.canvas.toDataURL());
          _this.canvas.onmousemove = null;
        }
      },
      // 绘制矩形
      createR(e, status, r1, r2) {
        let _this = this;
        let r;
        if (status == "begin") {
          console.log("onmousemove");
          _this.canvas.onmousemove = function (e) {
            _this.reset();
            let rx = e.layerX - r1;
            let ry = e.layerY - r2;
 
            //保留之前绘画的图形
            if (_this.step !== 0) {
              let canvasPic = new Image();
              canvasPic.src = _this.canvasHistory[_this.step];
              _this.ctx.drawImage(canvasPic, 0, 0);
            }
 
            _this.ctx.beginPath();
            _this.ctx.strokeRect(r1, r2, rx, ry);
            _this.ctx.strokeStyle = _this.strokeStyle;
            _this.ctx.lineWidth = _this.lineWidth;
            _this.ctx.closePath();
            _this.ctx.stroke();
          };
        } else if (status == "end") {
          _this.rebroadcast();
          let interval = setInterval(() => {
            if (_this.loading) {
              clearInterval(interval);
              _this.loading = false;
            } else {
              return;
            }
            let rx = e.layerX - r1;
            let ry = e.layerY - r2;
            _this.ctx.beginPath();
            _this.ctx.rect(r1, r2, rx, ry);
            _this.ctx.strokeStyle = _this.strokeStyle;
            _this.ctx.lineWidth = _this.lineWidth;
            _this.ctx.closePath();
            _this.ctx.stroke();
            _this.step = _this.step + 1;
            if (_this.step < _this.canvasHistory.length - 1) {
              _this.canvasHistory.length = _this.step; // 截断数组
            }
            _this.canvasHistory.push(_this.canvas.toDataURL());
            _this.canvas.onmousemove = null;
          }, 1);
        }
      },
 
      //绘制箭头
      drawArrow(e, status) {
        let _this = this;
        if (status == "begin") {
          //获取起始位置
          _this.arrowFromX = e.layerX;
          _this.arrowFromY = e.layerY;
          _this.ctx.beginPath();
          _this.ctx.moveTo(e.layerX, e.layerY);
        } else if (status == "end") {
          //计算箭头及画线
          let toX = e.layerX;
          let toY = e.layerY;
          let theta = 30;
          let headlen = 10;
          let _this = this;
          let fromX = this.arrowFromX;
          let fromY = this.arrowFromY;
          // 计算各角度和对应的P2,P3坐标
          let angle = Math.atan2(fromY - toY, fromX - toX) * 180 / Math.PI,
          angle1 = (angle + theta) * Math.PI / 180,
          angle2 = (angle - theta) * Math.PI / 180,
          topX = headlen * Math.cos(angle1),
          topY = headlen * Math.sin(angle1),
          botX = headlen * Math.cos(angle2),
          botY = headlen * Math.sin(angle2);
          let arrowX = fromX - topX,
          arrowY = fromY - topY;
          _this.ctx.moveTo(arrowX, arrowY);
          _this.ctx.moveTo(fromX, fromY);
          _this.ctx.lineTo(toX, toY);
          arrowX = toX + topX;
          arrowY = toY + topY;
          _this.ctx.moveTo(arrowX, arrowY);
          _this.ctx.lineTo(toX, toY);
          arrowX = toX + botX;
          arrowY = toY + botY;
          _this.ctx.lineTo(arrowX, arrowY);
          _this.ctx.strokeStyle = _this.strokeStyle;
          _this.ctx.lineWidth = _this.lineWidth;
          _this.ctx.stroke();
 
          _this.ctx.closePath();
          _this.step = _this.step + 1;
          if (_this.step < _this.canvasHistory.length - 1) {
            _this.canvasHistory.length = _this.step; // 截断数组
          }
          _this.canvasHistory.push(_this.canvas.toDataURL());
          _this.canvas.onmousemove = null;
        }
      },
 
      //文字输入
      createT(e, status) {
        let _this = this;
        if (status == "begin") {
 
        } else if (status == "end") {
          let offset = 0;
          if (_this.fontSize >= 28) {
            offset = (_this.fontSize / 2) - 3
          } else {
            offset = (_this.fontSize / 2) - 2
          }
 
          _this.ctxX = e.layerX + 2;
          _this.ctxY = e.layerY + offset;
 
          let index = this.getPointOnCanvas(e);
          _this.$refs.txt.style.left = index.x + 'px';
          _this.$refs.txt.style.top = index.y - (_this.fontSize / 2) + 'px';
          _this.$refs.txt.value = '';
          _this.$refs.txt.style.height = _this.fontSize + "px";
          _this.$refs.txt.style.width = _this.canvas.width - e.layerX - 1 + "px",
            _this.$refs.txt.style.fontSize = _this.fontSize + "px";
          _this.$refs.txt.style.fontFamily = _this.fontFamily;
          _this.$refs.txt.style.color = _this.fontColor;
          _this.$refs.txt.style.maxlength = Math.floor((_this.canvas.width - e.layerX) / _this.fontSize);
          _this.isShow = true;
          setTimeout(() => {
            _this.$refs.txt.focus();
          })
        }
      },
      //文字输入框失去光标时在画布上生成文字
      txtBlue() {
        let _this = this;
        let txt = _this.$refs.txt.value;
        if (txt) {
          _this.ctx.font = _this.$refs.txt.style.fontSize + ' ' + _this.$refs.txt.style.fontFamily;
          _this.ctx.fillStyle = _this.$refs.txt.style.color;
          _this.ctx.fillText(txt, _this.ctxX, _this.ctxY);
          _this.step = _this.step + 1;
          if (_this.step < _this.canvasHistory.length - 1) {
            _this.canvasHistory.length = _this.step; // 截断数组
          }
          _this.canvasHistory.push(_this.canvas.toDataURL());
          _this.canvas.onmousemove = null;
        }
      },
      //计算文字框定位位置
      getPointOnCanvas(e) {
        let cs = this.canvas;
        let content = document.getElementsByClassName("content")[0];
        return {
          x: e.layerX + (content.clientWidth - cs.width) / 2,
          y: e.layerY
        };
      },
      //清空文字
      resetTxt() {
        let _this = this;
        _this.$refs.txt.value = '';
        _this.isShow = false;
      }
    }
  };
</script>
<style scope>
  * {
    box-sizing: border-box;
  }
 
  body,
  html,
  #app {
    overflow: hidden;
  }
 
  .draw {
    height: 100%;
    min-width: 420px;
    display: flex;
    flex-direction: column;
  }
 
  .content {
    flex-grow: 1;
    height: 100%;
    width: 100%;
  }
 
  .drawTop {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    padding: 5px;
    height: 52px;
  }
 
  .drawTop > div {
    display: flex;
    align-items: center;
    padding: 5px 5px;
  }
 
  div.drawTopContrllor {
    display: none;
  }
 
  @media screen and (max-width: 1200px) {
    .drawTop {
      position: absolute;
      background-color: white;
      width: 100%;
      flex-direction: column;
      align-items: flex-start;
      height: 30px;
      overflow: hidden;
    }
 
    .drawTopContrllor {
      display: flex !important;
      height: 30px;
      width: 100%;
      justify-content: center;
      align-items: center;
      padding: 0 !important;
    }
  }
</style>

然后在页面中引入组件,传入图片链接。

在开发过程中遇到的问题

文字输入功能在用户输入文字后,如果不再点击别的地方直接点击别的功能按钮的话,最后输入的文字将不会再画布上生成,通过监控输入框的blur事件来在画布上生成文字,避免这个问题。

文字输入时字体的大小会影响生成文字的位置,这里发现文字的大小和位置有一个偏移量:

let offset = 0;
if (_this.fontSize >= 28) {
  offset = (_this.fontSize / 2) - 3
} else {
  offset = (_this.fontSize / 2) - 2
}

在画布上生成文字的时候需要加上这个偏移量,这里字体范围是14~36,别的字体大小没有校验,不一定适用这个计算方式。

绘制矩形的时候需要先清空画布,在清空之前先保存一次画布然后再清空再重新画一下画布,负责矩形框会不停的出现轨迹,并且之前画的元素会消失。

撤销的时候需要考虑文字输入,判断input得v-show是否为true,如果是true需要先清空文字,再撤销,否则画布上会一直存在一个输入框。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: vue下如何利用canvas实现在线图片标注

本文链接: https://www.lsjlt.com/news/147619.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • vue下如何利用canvas实现在线图片标注
    目录组件代码如下在开发过程中遇到的问题web端实现在线图片标注在此做下记录,功能类似微信截图时的标注,包含画线、框、箭头和文字输入,思路是利用canvas画布,先把要标注的图片使用d...
    99+
    2024-04-02
  • 在React中用canvas对图片标注的实现
    在审核业务中难免会有需要对图片进行标注的需求,本次用一个最小demo来演示如何对图片进行矩形标注。 首先我们要理解canvas是一块画布,而这块画布需要在我们要标注的图片上层,图片和...
    99+
    2024-04-02
  • 如何利用canvas实现图片压缩功能
    小编给大家分享一下如何利用canvas实现图片压缩功能,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!项目中做身份证识别时,需要传送图片的 base64 格式编码,...
    99+
    2023-06-09
  • 利用Python多线程实现图片下载器
    目录导语开发工具环境搭建原理简介效果展示导语 之前有很多小伙伴说想学习一下多线程图片下载器,虽然好像已经过去很久了,不过还是上来安排一波吧。至于题目为什么说是构建一个小型数据集,因为...
    99+
    2024-04-02
  • 如何利用canvas实现图片下载功能来实现浏览器兼容问题
    小编给大家分享一下如何利用canvas实现图片下载功能来实现浏览器兼容问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!前言:项目中需要实现图片下载功能,第一个想...
    99+
    2023-06-09
  • Canvas如何实现图片压缩
    这篇文章主要介绍Canvas如何实现图片压缩,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Canvas图片压缩流程接下来我将以具体实例为大家讲解Canvas图片压缩的具体流程。一、本...
    99+
    2024-04-02
  • Vue使用canvas实现图片压缩上传
    本文实例为大家分享了Vue使用canvas实现图片压缩上传的具体代码,供大家参考,具体内容如下 场景:如用户头像等 对于大尺寸图片的上传,在前端进行压缩除了省流量外,最大的意义是极大...
    99+
    2024-04-02
  • 如何在Retrofit中利用Rxjava实现一个图片下载功能
    这篇文章将为大家详细讲解有关如何在Retrofit中利用Rxjava实现一个图片下载功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。首先我们看一下Retrofit常规的用法,在不使用Rxj...
    99+
    2023-05-31
    retrofit rxjava ava
  • Vue如何实现鼠标悬浮切换图片
    这篇“Vue如何实现鼠标悬浮切换图片”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue如何实现鼠标悬浮切换图片”文章吧。需...
    99+
    2023-06-29
  • canvas如何实现图片镜像翻转
    这篇文章给大家分享的是有关canvas如何实现图片镜像翻转的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。 通过canvas自带的画布方法进行翻转  var img =&nb...
    99+
    2023-06-09
  • Vue下如何使用press实现图片放大功能
    本篇内容介绍了“Vue下如何使用press实现图片放大功能”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!VuePress 由两部分组成:一个...
    99+
    2023-07-04
  • JavaScript利用canvas实现炫酷的碎片切图效果
    目录前言需求分析实现过程坐标系切割绘制切割&渲染动画前言 今天分享一个炫酷的碎片式切图效果,这个其实在自己的之前的博客上有实现过,本人觉得这个效果还是挺炫酷的,这次还是用我们...
    99+
    2022-11-13
    JavaScript canvas碎片切图效果 JavaScript canvas 碎片切图 JavaScript canvas 切图
  • 如何使用html5 canvas实现图片玻璃碎片特效
    小编给大家分享一下如何使用html5 canvas实现图片玻璃碎片特效,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧! ...
    99+
    2024-04-02
  • HTML5 Canvas如何实现图片缩放比例
    小编给大家分享一下HTML5 Canvas如何实现图片缩放比例,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧! drawImage方法的又一变种是增加了两个用于控制图像在canvas中缩放...
    99+
    2024-04-02
  • 如何使用vue实现轮播图片
    这篇“如何使用vue实现轮播图片”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“如何使用vue实现轮播图片”文章吧。效果图案例...
    99+
    2023-07-02
  • HTML5如何使用Canvas实现放入图片和保存为图片功能
    这篇文章给大家分享的是有关HTML5如何使用Canvas实现放入图片和保存为图片功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。使用JavaScript将图片拷贝进画布要想将图...
    99+
    2024-04-02
  • php如何实现下载图片
    这篇文章主要介绍了php如何实现下载图片,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。php实现下载图片的方法:1、使用“file_get_contents”实现下载图片;2...
    99+
    2023-06-08
  • 如何利用python实现图片批处理
    前言 在训练神经网络之前,我们往往需要对数据集进行批量处理。本文以图片为例,介绍如何使用python实现图片的批量处理,包括批量命名,批量更改图像像素,批量对图片进行Harris、C...
    99+
    2024-04-02
  • 如何利用QT实现图片浏览器
    这篇文章主要介绍了如何利用QT实现图片浏览器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何利用QT实现图片浏览器文章都会有所收获,下面我们一起来看看吧。1、概述案例:制作一个小的图片浏览器,要求可以显示jp...
    99+
    2023-07-05
  • 如何利用React实现图片识别App
    先把效果图给大家放上来 个人觉得效果还行。识别不太准确是因为这个 app学习图片的时间太短(电脑太卡)。 (笔者是 window10) 安装运行环境: npm install ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作