点这里:本文仓库地址
界面:
这是一个基于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);
}
},
initSize()确定canvas的宽高等信息- 监听画布上鼠标移动事件
- 因为canvas有个特性,画布宽高改变的时候会清空画布,那么原本画在画布上的图片会消失,所以这里加了一个监听浏览器大小的事件
window.onresize,在其中,分别进行了清空画布、开启懒加载、加载url图片、根据数据源画框、选取第一个画框的操作 - 需要注意的:
- 因为业务需求里,需要动态传入url和locationDetile,并且要求一切(针对图片的缩放平移旋转)都恢复初始态,所以每次都需要给canvas加上
this.image.style.transform = "scale(1, 1) translate(0px, 0px) rotateZ(3600deg)";。这个根据自己的业务要求修改。 - 因为每次根据url在画布上画出图片(
loadImage())需要时间,所以根据数据源画框drawAll()这一动作需要在图片加载成功之后。有很多种方式,我这里使用了最笨的定时器。这个根据自己的业务要求修改。
- 因为业务需求里,需要动态传入url和locationDetile,并且要求一切(针对图片的缩放平移旋转)都恢复初始态,所以每次都需要给canvas加上
新增功能
-
传入一个坐标点对象,通过计算,根据输入数据渲染图形
-
通过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:图片实际大小/图片真实大小imagePosX、imagePosY:图片偏移量大小;以上三者都是初次渲染时,在加载图片的函数中计算得出。figureFactory():新建一个标注框对象,具体对象信息在packages\DrawBoard\draw\figureFactory.js,这里需要将四个坐标点以及配置信息注入该对象。并且调用对象中的drawMyPoint()方法去渲染出该标注框。
-
-
默认在图上画出标注框后,操作状态为修改,并选中第一个标注框
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; } } }, -
添加一个新增标注标识
isFocus,当isFocus为true时,画布自动切换为新增标注状态,并且在上方显示“标注”图标watch:{ isFocus: { handler() { if (this.isFocus) { this.activeIndex = -1; this.readyForNewEvent("draw"); } else { } }, immediate: true, }, } -
添加一个
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; }