Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何基于canvas做出一个绘制板工具 #39

Open
janeLLLL opened this issue Dec 3, 2020 · 0 comments
Open

如何基于canvas做出一个绘制板工具 #39

janeLLLL opened this issue Dec 3, 2020 · 0 comments

Comments

@janeLLLL
Copy link
Owner

janeLLLL commented Dec 3, 2020

本项目源于vue2-drawboard,修改了一些原有的bug,更改了选项配置,以及根据业务需求添加了一些功能。

点这里:本文仓库地址

界面:


这是一个基于vue2.x的绘制板工具。通过该工具,您可以在图片上标记您想要的信息,并获得相应的数据。此外,您还可以将其作为一个普通的画板来使用,您可以在上面自由地绘制图形。当前支持矩形画图。可以支持放大、缩小、旋转、平移等操作。此外,您可以灵活地配置您的绘图信息。

结构

D:.
├─lib
│  └─fonts
├─packages
│  └─DrawBoard
│      ├─assets
│      ├─components
│      ├─draw
│      ├─styles
│      └─utils
└─public

其中,packages\index.js为入口文件

参数选项

参数 类型 备注
locationDetile Object 根据输入数据渲染图形
url String 要编辑的图像的URL
isFocus Boolean 是否传入了当前修改框的坐标对象
point Object 当前修改框的坐标对象
loadingData Boolean 控制是否显示图像加载动画

数据格式特殊说明

locationDetile

根据输入数据渲染图形。数据格式如下:

{
    example1:[
        110,
        220,
        330,
        140,
        150,
        160,
        180,
        300
    ],
    example2:[
        110,
        220,
        330,
        140,
        150,
        160,
        180,
        300
    ]
}

point

point是根据实际业务要求,从父组件传入的,当前应修改框的位置信息对象,并在图上对应显示当前修改框的位置。你也可以根据自己的要求灵活更改。数据格式如下:

{
    location:[
    	110,
        220,
        330,
        140,
        150,
        160,
        180,
        300
    ]
}

关键流程图

url图片链接

locationDetile位置信息

功能详解

packages\DrawBoard\main.vue

初次渲染

mounted() {
    this.initSize();
    this.observerView();
    this.canvas.addEventListener(
      "mousemove",
      this.drawNavigationLineEvent,
      false
    );
    var that = this;
    window.onresize = () => {
      return (() => {
        if (this.url) {
          this.clearAll1();
          this.loading = true;
          this.loadImage(this.url);
          that.drawOnePoint();
        }
        this.image.style.transform =
          "scale(1, 1) translate(0px, 0px) rotateZ(3600deg)";
        this.clearAll1();
        setTimeout(function () {
          that.drawAll();
          that.drawOnePoint();
        }, 800);
      })();
    };
    if (!this.isMounted) {
      setTimeout(function () {
        that.drawOnePoint();
      }, 900);
    }
  },
  1. initSize()确定canvas的宽高等信息
  2. 监听画布上鼠标移动事件
  3. 因为canvas有个特性,画布宽高改变的时候会清空画布,那么原本画在画布上的图片会消失,所以这里加了一个监听浏览器大小的事件window.onresize,在其中,分别进行了清空画布、开启懒加载、加载url图片、根据数据源画框、选取第一个画框的操作
  4. 需要注意的:
    • 因为业务需求里,需要动态传入url和locationDetile,并且要求一切(针对图片的缩放平移旋转)都恢复初始态,所以每次都需要给canvas加上this.image.style.transform = "scale(1, 1) translate(0px, 0px) rotateZ(3600deg)";。这个根据自己的业务要求修改。
    • 因为每次根据url在画布上画出图片(loadImage())需要时间,所以根据数据源画框drawAll()这一动作需要在图片加载成功之后。有很多种方式,我这里使用了最笨的定时器。这个根据自己的业务要求修改。

新增功能

  1. 传入一个坐标点对象,通过计算,根据输入数据渲染图形

    • 通过watch监听器监听locationDetile属性:

      watch:{
      	locationDetile: {
            handler() {
              if (Object.keys(this.locationDetile).length !== 0) {
                var that = this;
                this.clearAll1();
                setTimeout(function () {
                  that.drawAll();
                  that.drawOnePoint();
                }, 800);
                this.lastPoint = [];
                this.point = [];
              } else {
              }
            },
            deep: true,
            immediate: true,
          },
      }

    注意:为了避免监听器反复监听,在图片上进行多次标注,所以每次画框都需要清空一次画布

    • 通过drawAll()按照原始图片比例,换算成当前画布比例,这样就保证了无论画布显示多大,标注框都能在正确位置:

          //统一画框
          drawAll() {
            this.image.style.transform =
              "scale(1, 1) translate(0px, 0px) rotateZ(3600deg)";
            this.clearAll1();
            const canvas = document.getElementById("canvas");
            const ctx = canvas.getContext("2d");
            const img = document.getElementById("image");
            const ctx1 = img.getContext("2d");
            for (let key in this.locationDetile) {
              if (key !== "imgSize" && this.locationDetile[key] !== "Null") {
                const firstPoint = {
                  x: this.locationDetile[key][0] * this.imageScale + this.imagePosX,
                  y: this.locationDetile[key][1] * this.imageScale + this.imagePosY,
                };
                const rectSize = {
                  w:
                    (this.locationDetile[key][2] - this.locationDetile[key][0]) *
                    this.imageScale,
                  h:
                    (this.locationDetile[key][5] - this.locationDetile[key][1]) *
                    this.imageScale,
                };
                this.activeGraphic = figureFactory(
                  this.currentTool,
                  { x: firstPoint.x, y: firstPoint.y },
                  this.options
                );
                this.activeGraphic.points = [
                  { x: firstPoint.x, y: firstPoint.y },
                  { x: firstPoint.x + rectSize.w, y: firstPoint.y },
                  { x: firstPoint.x + rectSize.w, y: firstPoint.y + rectSize.h },
                  { x: firstPoint.x, y: firstPoint.y + rectSize.h },
                ];
                this.graphics.push(this.activeGraphic);
                this.activeIndex = this.graphics.length - 1;
                this.activeGraphic.drawMyPoint(ctx, firstPoint, rectSize);
              }
            }
            this.drawBG();
            this.drawGraphics();
            this.loading = false;
          },

      imageScale:图片实际大小/图片真实大小

      imagePosXimagePosY:图片偏移量大小;以上三者都是初次渲染时,在加载图片的函数中计算得出。

      figureFactory():新建一个标注框对象,具体对象信息在packages\DrawBoard\draw\figureFactory.js,这里需要将四个坐标点以及配置信息注入该对象。并且调用对象中的drawMyPoint()方法去渲染出该标注框。

  2. 默认在图上画出标注框后,操作状态为修改,并选中第一个标注框

    drawOnePoint() {
          for (var i = 0; i < this.graphics.length; i++) {
            if (this.graphics[i].x + "" != "NaN") {
              this.graphics[i].drawPoints(this.canvasCtx);
              this.activeIndex = i;
              this.currentStatus = status.UPDATING;
              this.drawBG();
              this.drawGraphics();
              break;
            }
          }
        },

  3. 添加一个新增标注标识isFocus,当isFocus为true时,画布自动切换为新增标注状态,并且在上方显示“标注”图标

    watch:{
        isFocus: {
          handler() {
            if (this.isFocus) {
              this.activeIndex = -1;
              this.readyForNewEvent("draw");
            } else {
            }
          },
          immediate: true,
        },
    }
  4. 添加一个point属性,当point有值传入的时候,可以判断出该位置是否属于已经标注好的框中,并且默认选中修改。

    • 监听器
    watch:{
        point: {
          handler() {
            if (Object.keys(this.point).length !== 0) {
              if (this.point !== this.lastPoint) {
              } else {
              }
              this.lastPoint = this.point;//记录上一个点
              this.drawAll();
              this.updatePoint();
              this.drawGraphics();
            } else {
              this.clearAll1()
              this.drawAll();
              this.drawBG();
              this.currentStatus = status.MOVING;
            }
          },
          deep: true,
          immediate: true,
        },
    }
    • 判断是否在框内
        updatePoint() {
          for (let i = 0; i < this.graphics.length; i++) {
            if (
              this.graphics[i].isInPath(
                this.canvasCtx,
                this.transPoint(this.point.location[0], this.point.location[1])
              ) > -1
            ) {
              this.canvas.style.cursor = "crosshair";
              this.activeGraphic = this.graphics[i];
              console.log("activeIndex", this.activeIndex);
              this.activeIndex = i;
              this.currentStatus = status.UPDATING;
              this.drawBG();
              break;
            }
          }
        },
    • 关键函数isInPath()
    //packages\DrawBoard\draw\figureFactory.js
    isInPath(ctx, point) {
        for (let i = 0; i < this.points.length; i++) {
          // 通过清空子路径列表开始一个新路径的方法。 当你想创建一个新的路径时,调用此方法。
          ctx.beginPath();
          //绘制圆弧路径的方法
          ctx.arc(this.points[i].x, this.points[i].y, this.point_radis, 0, Math.PI * 2, false);
          if (ctx.isPointInPath(point.x, point.y)) {
            return i;
          }
        }
        // in the figure
        this.createPath(ctx);
        if (ctx.isPointInPath(point.x, point.y)) {
          return 999;
        }
        return -1;
      }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant