一、案例截图
文章记录了工作中开发的标注功能,上图是写的一个demo,不是实际项目中的代码和页面,所以页面和代码写的很潦草只是为了记录下,忽略页面样式和一些细节。代码写的很烂,别怼我!!!
二、简介
在做项目时会遇到手动绘图等标注的需求,此时多会想到使用canvas,但如果自己用原生canvas去画图、尤其是拖拽交互会复杂很多;这时候可以考虑fabric.js库来实现。
自己理解Fabric的话,就是在原生canvas上封装了一次,代码语义上更加简洁易懂,暴露的api可供我们直接调用,可以调用不同的api直接创建出矩形、圆形、直线、文本等不同的图形,并且有鼠标的一些事件可供使用;
本工具中包含了所有的常用图形,所以直接复制代码到你的项目中即可看到效果。
另外fabric是外国的框架,官网是纯英文的,你**🐴😡,我是看不懂,太欺负人了,所以就找到了一个其他博主简单翻意的算是一个中文文档吧,参考下,直接上链接;
官网网址:fabricjs.com/
官网提供的demo例子:fabricjs.com/demos/
引用 中文文档:funcion_woqu.gitee.io/fabric-doc/…
npm地址:www.npmjs.com/package/fab…
三、相关依赖
文章代码中使用到了几个依赖库,需要下载
1、element-ui的取色器、input、Select组件
2、fabric.js (从上面👆🏻的npm官网下载)
四、源码
<template>
<div class="fabric">
<div class="controlPanel">
<div
:class="[currentTool === item.name ? 'contro-item active' : 'contro-item']"
v-for="(item, idx) in toolsArr"
:key="idx"
@click="handleTools(item, idx)"
>
<div v-if="!item.custom">
<span class="con_title">{{ item.title }}</span>
</div>
<div v-if="item.name == 'ColorPicker'">
<span class="con_title">{{ item.title }}:</span>
<el-color-picker
v-model="stroke"
show-alpha
:predefine="predefineColors">
</el-color-picker>
</div>
<div v-if="item.name == 'lineWidth'" class="_inp">
<span class="con_title">{{ item.title }}:</span>
<el-input
size="mini"
type="number"
v-model="strokeWidth"
@blur="blurChang"
@input="inpChang"
placeholder="请输入线宽"
></el-input>
</div>
<div v-if="item.name == 'fontSize'">
<span class="con_title">{{ item.title }}:</span>
<el-select v-model="textFontSize" placeholder="请选择" size="mini">
<el-option
v-for="(size_item,size_index) in [15,18,21,24]"
:key="size_item"
:label="size_item"
:value="size_item">
</el-option>
</el-select>
</div>
<div v-if="item.name == 'Stamp'" class="aa">
<el-upload
class="upload-demo"
ref="upload"
action="fakeaction"
:http-request="uploadSectionFile"
accept=".jpg,.png"
:show-file-list="false"
:multiple="true"
>
<span class="con_title">{{ item.title }}</span>
</el-upload>
</div>
</div>
</div>
<div class="canvas-wraper">
<p class="title">绘图区</p>
<canvas width="600" height="600" id="canvas" style="border: 1px solid #ccc;"></canvas>
</div>
<div class="imgbase">
<p class="title">图片展示区</p>
<img :src="imageBase64" v-show="imageBase64!=''" alt="">
</div>
</div>
</template>
<script>
//创建完实例后,fabric.js会构建两层 canvas 元素:lower-canvas 和 upper-canvas
// lower-canvas: 只负责渲染元素
// upper-canvas: 负责所有的事件处理
import { fabric } from "fabric";
export default {
name: "Fabric",
data() {
return {
fabricCanvas: null, // 当前画布,如果需求是多个页可以循环
drawingObject: null, // 当前fabric对象
currentTool: "juxing",
toolsArr: [
{ name: "ColorPicker", title: "取色器", icon: "", custom: true },
{ name: "lineWidth", title: "线宽", icon: "", custom: true },
{ name: "fontSize", title: "字号", icon: "", custom: true },
{ name: "bj", title: "编辑", icon: "", custom: false },
{ name: "pencil", title: "自由画笔", icon: "", custom: false },
{ name: "line", title: "直线", icon: "", custom: false },
{ name: "arrow", title: "箭头", icon: "", custom: false },
{ name: "xuxian", title: "虚线", icon: "", custom: false },
{ name: "wavyLine", title: "波浪线", icon: "", custom: false },
{ name: "juxing", title: "矩形", icon: "", custom: false },
{ name: "Highlight", title: "高亮", icon: "", custom: false },
{ name: "Stamp", title: "图章", icon: "", custom: true },
{ name: "text", title: "文字", icon: "", custom: false },
{ name: "cricle", title: "圆形", icon: "", custom: false },
{ name: "ellipse", title: "椭圆", icon: "", custom: false },
{ name: "equilateral", title: "三角形", icon: "", custom: false },
{ name: "undo", title: "上一步", icon: "", custom: false },
{ name: "redo", title: "下一步", icon: "", custom: false },
{ name: "reset", title: "重置", icon: "", custom: false },
{ name: "remove", title: "删除", icon: "", custom: false },
{ name: "bc", title: "获取所有图形数据", icon: "", custom: false },
{ name: "imageBase64", title: "保存成图片", icon: "", custom: false },
],
mouseFrom: {},
mouseTo: {},
moveCount: 1,
doDrawing: false,
fabricHistoryJson: [],
mods: 0,
predefineColors: [
'#ff4500',
'#ff8c00',
'#ffd700',
'#90ee90',
'#00ced1',
'#1e90ff',
'#c71585',
'rgba(255, 69, 0, 0.68)',
'rgb(255, 120, 0)',
'hsv(51, 100, 98)',
'hsva(120, 40, 94, 0.5)',
'hsl(181, 100%, 37%)',
'hsla(209, 100%, 56%, 0.73)',
'#c7158577',
],
stroke: "#00ced1", // 组件默认颜色 rgba(227, 79, 81) 或 #E34F51
strokeWidth: 1, // 组件画笔的宽度
textFontSize: 18, // 文本字体大小
imageBase64: '',
};
},
computed: {
},
mounted() {
this.initCanvas()
},
watch: {
stroke(newvalu,oldvalue){
if (this.drawingObject) {
var drawingObject = this.drawingObject
this.fabricCanvas.remove(this.drawingObject);
this.drawingObject = drawingObject
this.drawingObject.set({
stroke: this.stroke,
})
if (this.drawingObject.customType == 'Highlight') {
this.drawingObject.set({
fill: this.stroke,
})
}
this.fabricCanvas.add(this.drawingObject);
this.fabricCanvas.setActiveObject(this.drawingObject)
this.fabricCanvas.requestRenderAll()
}
},
strokeWidth(newvalu,oldvalue) {
if (this.strokeWidth != 0 && this.drawingObject && this.drawingObject.customType != 'Highlight') {
var drawingObject = this.drawingObject
this.fabricCanvas.remove(this.drawingObject);
this.drawingObject = drawingObject
this.drawingObject.set({
strokeWidth: this.strokeWidth,
})
this.fabricCanvas.add(this.drawingObject);
this.fabricCanvas.setActiveObject(this.drawingObject)
this.fabricCanvas.requestRenderAll()
}
},
},
methods: {
// 重置画布
resetCon() {
this.fabricCanvas.clear(); // 清空画布
this.fabricHistoryJson = []; // 清空历史记录
},
blurChang(vlaue){
this.strokeWidth = this.strokeWidth == 0 ? 1 : this.strokeWidth
},
inpChang(value) {
this.strokeWidth = Number(value);
if (this.strokeWidth < 0) {
this.strokeWidth = 1;
}
},
// 选取图章文件
uploadSectionFile(params){
console.log('选取文件',params)
const file = params
const _this = this
const reader = new FileReader()
reader.readAsDataURL(file.file)
reader.onload = () => {
// reader.result 是 base64格式
fabric.Image.fromURL(reader.result, oImg => {
console.log('oImg>>>:',oImg)
oImg.set({
customType: 'Stamp',
type: 'Stamp',
width: oImg.width,
height: oImg.height,
left: 50,
top: 50,
stroke: this.stroke,
strokeWidth: 0,
fill: "rgba(255,255,255,0)",
img: reader.result,
imgName: file.file.name,
hasControls: false, // 不显示控制器;如果图片需要手动调整大小,注释掉这行
})
_this.drawingObject = oImg
_this.fabricCanvas.add(oImg);
_this.fabricCanvas.discardActiveObject()
_this.fabricCanvas.setActiveObject(oImg)
_this.fabricCanvas.requestRenderAll()
})
}
},
// 初始化fabric
initCanvas() {
this.fabricCanvas = new fabric.Canvas("canvas", {
isDrawingMode: false, //设置是否可以自由绘制
selection: false, // 是否允许框选 默认true true允许 false不允许
skipTargetFind: false, // 是否选中 默认false true禁止 false允许
width: 1100, //设置画布的宽度
height: 300, //设置画布的高度
});
//另一种设置宽高的方式
// this.fabricCanvas.setWidth(100); //设置画布的宽度
// this.fabricCanvas.setHeight(100); //设置画布的高度
this.fabricObjAddEvent(); //绑定画板事件
this.LineWithArrow()
this.keyDown();
},
//事件监听
fabricObjAddEvent() {
var _this = this;
this.fabricCanvas.on({
// 鼠标按下
"mouse:down": (e) => {
console.log("鼠标按下", e);
_this.drawingObject = null;
_this.mouseFrom.x = e.pointer.x;
_this.mouseFrom.y = e.pointer.y;
_this.doDrawing = true;
// 创建文字是在鼠标按下时创建,不可与绘制矩形箭头等图像一样放在鼠标移动时
// 鼠标按下获取处于活动状态的对象 (此行代码的作用:防止鼠标在拖动时,调用drawing方法)
if (_this.currentTool == "text" && !_this.fabricCanvas.getActiveObject()) {
_this.drawText();
}
},
// 鼠标移动
"mouse:move": (e) => {
// console.log('鼠标移动',e)
if (_this.moveCount % 2 && !_this.doDrawing) {
//减少绘制频率
return;
}
_this.moveCount++;
_this.mouseTo.x = e.pointer.x;
_this.mouseTo.y = e.pointer.y;
// 鼠标按下获取处于活动状态的对象 (此行代码的作用:防止鼠标在拖动时,调用drawing方法)
if (!_this.fabricCanvas.getActiveObject()) {
_this.drawing();
}
},
// 鼠标抬起
"mouse:up": (e) => {
console.log("鼠标抬起", e);
_this.mouseTo.x = e.pointer.x;
_this.mouseTo.y = e.pointer.y;
_this.moveCount = 1;
_this.doDrawing = false;
if (e.target || _this.drawingObject) {
_this.fabricCanvas.discardActiveObject()
_this.fabricCanvas.setActiveObject(e.target || _this.drawingObject)
_this.fabricCanvas.requestRenderAll()
}
_this.updateModifications(true);
},
// 选中画布上的对象
"selection:created": (e) => {
console.log('选中画布上的对象:',e)
// 设置Object控制器样式
e.target.set({
transparentCorners: false,
cornerColor: this.stroke,
cornerSize: 12,
cornerStyle: 'circle', // 设置Object控制器四角的样式为circle圆角
padding: 10,
borderColor: this.stroke, // 设置Object为激活状态时,控制器的边框颜色。默认值为 //rgba(102,153,255,0.75)。
borderDashArray: [3, 3],
// cornerStrokeColor: '#ff7a55',
});
_this.drawingObject = e.target;
_this.strokeWidth = e.target.strokeWidth
_this.stroke = e.target.stroke
if (e.target.fontSize) {
_this.textFontSize = e.target.fontSize
}
_this.removeFabricObj(e.target);
// _this.fabricCanvas.discardActiveObject(); //清楚选中框
// _this.updateModifications(true);
},
// 取消选中
"selection:cleared":(e) => {
console.log('取消选中',e)
_this.drawingObject = null;
_this.strokeWidth = _this.strokeWidth == 0 ? 1 : _this.strokeWidth
},
//对象移动
"object:moving": (e) => {
console.log("对象移动", e);
e.target.opacity = 0.5;
_this.noOutgoingLine(e,'moving'); // 禁止元素超出画布
},
// 对象缩放
"object:scaling": (e) => {
console.log('对象缩放',e)
},
"object:modified": (e) => {
console.log("object:modified:", e);
e.target.opacity = 1;
_this.updateModifications(true);
},
//选中对象更新
"selection:updated": (e) => {
console.log("选中对象更新:", e);
},
//增加对象
"object:added": (e) => {
// console.log('增加对象:',e)
// debugger
},
});
},
// 禁止元素超出画布
noOutgoingLine(e,strType){
if (strType == 'moving') {
const padding = -10; // 内容距离画布的空白宽度,主动设置
const obj = e.target;
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// 处理条件, 如果元素距离听小于 padding 或者距离 左侧小于 padding top-left corner
if(obj.getBoundingRect().top < padding || obj.getBoundingRect().left < padding){
const toTop = (obj.top-obj.getBoundingRect().top) + padding
const toLeft = (obj.left-obj.getBoundingRect().left) + padding
// 如果元素只有一个条件在阀值,则另外一个条件正常处理
obj.top = Math.max(obj.top, toTop);
obj.left = Math.max(obj.left, toLeft);
}
// 元素距离上侧的距离 + 元素的高度, 计算元素距离底部的距离
const toHeight = obj.getBoundingRect().top + obj.getBoundingRect().height;
// 元素距离左侧的距离 + 元素的宽度 , 计算元素距离右边的距离
const toWidth = obj.getBoundingRect().left + obj.getBoundingRect().width;
// 处理条件 如果元素距离底部小于 padding, 或者距离右边小于padding bottom-right corner
if(toHeight > obj.canvas.height - padding || toWidth > obj.canvas.width - padding){
const toTop = obj.top - (toHeight - (obj.canvas.height - padding));
const toLeft = obj.left - (toWidth - (obj.canvas.width - padding));
obj.top = Math.min(obj.top, toTop);
obj.left = Math.min(obj.left, toLeft);
}
}
},
// 注册键盘退格键删除
keyDown() {
document.onkeydown = (e) => {
console.log(e)
if (e && e.keyCode == 46 && this.drawingObject) {
this.removeFabricObj(this.drawingObject,'backspaceKey')
}
};
},
// 删除选中的对象
removeFabricObj(e_target,type) {
console.log('删除选中的对象:',e_target)
if (!e_target) {
return
}
if (this.currentTool == "remove" || type == 'backspaceKey') {
if (e_target._objects) {
//多选删除
var etCount = e_target._objects.length;
for (var etindex = 0; etindex < etCount; etindex++) {
this.fabricCanvas.remove(e_target._objects[etindex]);
}
} else {
//单选删除
this.fabricCanvas.remove(e_target);
}
}
},
//储存历史记录
updateModifications(savehistory) {
if (savehistory == true) {
this.fabricHistoryJson.push(JSON.stringify(this.fabricCanvas));
}
},
// 上一步
undo() {
let state = this.fabricHistoryJson;
if (this.mods < state.length) {
this.fabricCanvas.clear().renderAll();
this.fabricCanvas.loadFromJSON(
state[state.length - 1 - this.mods - 1]
);
this.fabricCanvas.renderAll();
this.mods += 1;
}
},
// 下一步
redo() {
let state = this.fabricHistoryJson;
if (this.mods > 0) {
this.fabricCanvas.clear().renderAll();
this.fabricCanvas.loadFromJSON(
state[state.length - 1 - this.mods + 1]
);
this.fabricCanvas.renderAll();
this.mods -= 1;
}
},
handleTools(tools, idx) {
this.fabricCanvas.isDrawingMode = false
this.fabricCanvas.selection = false
this.fabricCanvas.skipTargetFind = false
this.currentTool = tools.name;
switch (tools.name) {
case "pencil":
this.fabricCanvas.isDrawingMode = true
this.fabricCanvas.freeDrawingBrush.color = this.stroke; // 设置绘画笔的颜色
this.fabricCanvas.freeDrawingBrush.width = this.strokeWidth; // 设置绘画笔的宽度
break;
case "remove":
case "bj":
this.fabricCanvas.selection = true
break;
case "redo":
this.redo();
break;
case "undo":
this.undo();
break;
case "reset":
this.resetCon();
case "bc":
this.addAnnot();
case "imageBase64":
this.downLoadImage()
break;
default:
break;
}
},
drawing() {
if (this.drawingObject) {
this.fabricCanvas.remove(this.drawingObject);
}
let _this = this;
let fabricObject = null;
switch (this.currentTool) {
case "line": // 直线
fabricObject = new fabric.Line([
this.mouseFrom.x,
this.mouseFrom.y,
this.mouseTo.x,
this.mouseTo.y,
]);
break;
case "arrow": // 箭头
fabricObject = new fabric.Path(
this.drawArrow(
this.mouseFrom.x,
this.mouseFrom.y,
this.mouseTo.x,
this.mouseTo.y,
17.5,
17.5
));
break;
case "xuxian": //虚线
fabricObject = new fabric.Line(
[
this.mouseFrom.x,
this.mouseFrom.y,
this.mouseTo.x,
this.mouseTo.y,
],
{
strokeDashArray: [10, 3],
}
);
break;
case "wavyLine": // 波浪线
// 方式一:绘制任意方向的波浪线
fabricObject = new fabric.LineWithArrow([
this.mouseFrom.x,
this.mouseFrom.y,
this.mouseTo.x,
this.mouseTo.y,
])
// 方式二:只可绘制水平波浪线
// fabricObject = new fabric.Path(
// this.drawWavyLine(
// this.mouseFrom.x,
// this.mouseFrom.y,
// this.mouseTo.x,
// this.mouseTo.y,
// 5,
// 5
// )
// );
break;
case "Highlight": // 高亮
case "juxing": //矩形
fabricObject = new fabric.Rect({
top: Math.min(this.mouseFrom.y, this.mouseTo.y),
left: Math.min(this.mouseFrom.x, this.mouseTo.x),
width: Math.abs(this.mouseFrom.x - this.mouseTo.x),
height: Math.abs(this.mouseFrom.y - this.mouseTo.y),
});
break;
case "cricle": //正圆
let radius = Math.sqrt(
(this.mouseTo.x - this.mouseFrom.x) *
(this.mouseTo.x - this.mouseFrom.x) +
(this.mouseTo.y - this.mouseFrom.y) *
(this.mouseTo.y - this.mouseFrom.y)
) / 2;
fabricObject = new fabric.Circle({
left: this.mouseFrom.x,
top: this.mouseFrom.y,
radius: radius,
});
break;
case "ellipse": //椭圆
let left = this.mouseFrom.x;
let top = this.mouseFrom.y;
fabricObject = new fabric.Ellipse({
left: left,
top: top,
originX: "center",
originY: "center",
rx: Math.abs(left - this.mouseTo.x),
ry: Math.abs(top - this.mouseTo.y),
});
break;
case "equilateral": //等边三角形
let height = this.mouseTo.y - this.mouseFrom.y;
fabricObject = new fabric.Triangle({
top: this.mouseFrom.y,
left: this.mouseFrom.x,
width: Math.sqrt(Math.pow(height, 2) + Math.pow(height / 2.0, 2)),
height: height,
});
break;
case "remove":
break;
default:
break;
}
if (fabricObject) {
// 设置Object其它属性
fabricObject.set({
customType: this.currentTool, // 自定义的类型,如果不需要可以删掉
stroke: this.stroke,
strokeWidth: this.strokeWidth,
fill: "rgba(255,255,255,0)", // 绘制空心图形时fill为透明,绘制实心图形时fill需要设置颜色
// hasControls: false, // 是否显示控制器 false不显示
});
if (this.currentTool == 'Highlight') {
fabricObject.set('fill', this.stroke); // 本例中高亮其实就是一个实心的矩形图
fabricObject.set('strokeWidth', 0); // 高亮的图形没有线宽
}
this.fabricCanvas.add(fabricObject);
this.drawingObject = fabricObject;
}
},
// 自定义波浪线
LineWithArrow(){
if (fabric.LineWithArrow) {
fabric.warn('fabric.LineWithArrow is already defined.');
return;
}
fabric.LineWithArrow = fabric.util.createClass(fabric.Line, {
type: 'line_with_arrow',
initialize: function(element, options) {
options || (options = {});
this.callSuper('initialize', element, options);
// Set default options
this.set({
hasBorders: false,
hasControls: false,
});
},
_render: function(ctx) {
// this.callSuper('_render', ctx);
ctx.save();
const xDiff = this.x2 - this.x1;
const yDiff = this.y2 - this.y1;
const angle = Math.atan2(yDiff, xDiff);
ctx.translate(xDiff / 2, yDiff / 2);
ctx.rotate(angle);
ctx.beginPath();
// Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
ctx.moveTo(5, 0);
ctx.lineTo(-5, 5);
ctx.lineTo(-5, -5);
ctx.closePath();
ctx.fillStyle = this.stroke;
ctx.fill();
ctx.restore();
var p = this.calcLinePoints();
var point = this.pointOnLine(this.point(p.x2, p.y2), this.point(p.x1, p.y1), 10)
this.wavy(this.point(p.x1, p.y1), point, this.point(p.x2, p.y2), ctx);
ctx.stroke();
},
point: function(x, y) {
return {
x: x,
y: y
};
},
wavy: function(from, to, endPoint, ctx) {
var cx = 0,
cy = 0,
fx = from.x,
fy = from.y,
tx = to.x,
ty = to.y,
i = 0,
step = 4,
waveOffsetLength = 0,
ang = Math.atan2(ty - fy, tx - fx),
distance = Math.sqrt((fx - tx) * (fx - tx) + (fy - ty) * (fy - ty)),
amplitude = -10,
f = Math.PI * distance / 30;
for (i; i <= distance; i += step) {
waveOffsetLength = Math.sin((i / distance) * f) * amplitude;
cx = from.x + Math.cos(ang) * i + Math.cos(ang - Math.PI / 2) * waveOffsetLength;
cy = from.y + Math.sin(ang) * i + Math.sin(ang - Math.PI / 2) * waveOffsetLength;
i > 0 ? ctx.lineTo(cx, cy) : ctx.moveTo(cx, cy);
}
ctx.lineTo(to.x, to.y);
ctx.lineTo(endPoint.x, endPoint.y);
},
pointOnLine: function(point1, point2, dist) {
var len = Math.sqrt(((point2.x - point1.x) * (point2.x - point1.x)) + ((point2.y - point1.y) * (point2.y - point1.y)));
var t = (dist) / len;
var x3 = ((1 - t) * point1.x) + (t * point2.x),
y3 = ((1 - t) * point1.y) + (t * point2.y);
return new fabric.Point(x3, y3);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
customProps: this.customProps,
});
},
})
},
// 绘制文字对象
drawText() {
this.drawingObject = new fabric.Textbox("", {
left: this.mouseFrom.x,
top: this.mouseFrom.y,
width: 150,
fontSize: this.textFontSize,
fill: this.stroke,
hasControls: false,
type: "Watermark",
customType: this.currentTool,
stroke: this.stroke,
lineHeight: 1,
// fontWeight: "bold",// 设置文本的粗细
textAlign: "left", // 文字对齐
splitByGrapheme: true, // 拆分中文,可以实现自动换行
objectCaching: false,
});
this.fabricCanvas.add(this.drawingObject);
this.drawingObject.enterEditing();
this.drawingObject.hiddenTextarea.focus();
this.updateModifications(true);
},
// 绘制波浪线 (只可水平绘制)
drawWavyLine(x1, y1, x2, y2, xOff, yOff) {
let a = new Array();
let xLen = (x2 - x1) / xOff;
for(let i = 0; i < xLen; i++){
a.push({x : x1 + xOff * i, y:i % 2 == 0 ? y1 + yOff : y1})
}
var path = ''
for(let i = 1; i < a.length; i++){
path += ` L ${a[i].x} ${a[i].y}`
}
if (a.length > 0) {
path = `M ${a[0].x} ${a[0].y} ${path}`
}
return path;
},
// 绘制箭头
drawArrow(fromX, fromY, toX, toY, theta, headlen, _type) {
theta = typeof theta != "undefined" ? theta : 30;
headlen = typeof theta != "undefined" ? headlen : 10;
// 计算各角度和对应的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;
let path = "";
if (_type == "arrowAdd") {
var pathObj = {
M1: { x: fromX, y: fromY },
L2: { x: toX, y: toY },
M3: { x: toX + topX, y: toY + topY },
L4: { x: toX, y: toY },
L5: { x: toX + botX, y: toY + botY },
};
return pathObj;
}
path = "M " + fromX + " " + fromY;
path += " L " + toX + " " + toY;
arrowX = toX + topX;
arrowY = toY + topY;
path += " M " + arrowX + " " + arrowY;
path += " L " + toX + " " + toY;
arrowX = toX + botX;
arrowY = toY + botY;
path += " L " + arrowX + " " + arrowY;
console.log("path:", path);
return path;
},
// 获取绘制好的所有对象数据
addAnnot() {
console.log(this.fabricCanvas.getObjects()) // 获取画布容器中的所有对象
},
downLoadImage() {
//生成双倍像素比的图片
let base64URl = this.fabricCanvas.toDataURL({
formart: 'png',
multiplier: 1, // 倍数
})
this.imageBase64 = base64URl
console.log(base64URl)
},
},
components: {},
};
</script>
<style scoped>
/* 去除 el-input type为number时,输入框中的上下滚动按钮 */
::v-deep ._inp input::-webkit-outer-spin-button,
::v-deep ._inp input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
/* 工具栏 start */
.controlPanel {
width: 100%;
display: flex;
align-items: center;
white-space: nowrap;
flex-wrap: wrap;
}
.controlPanel ._inp .el-input{
width: 70%;
}
.controlPanel .contro-item {
margin-left: 1.5rem;
text-align: center;
cursor: pointer;
}
.controlPanel .active {
color: rgba(30, 144, 255, 1);
}
.controlPanel .contro-item .picker{
width: 25px;
height: 25px;
border: 2.5px solid transparent;
border-radius: 50%;
background-clip: padding-box, border-box;
background-origin: padding-box, border-box;
background-image: linear-gradient(to right, rgba(30, 144, 255, 1), rgba(30, 144, 255, 1), linear-gradient(90deg, #8F41E9, #578AEF));
}
/* 工具栏 end */
/* 绘图区 start */
.canvas-wraper {
border: 1px solid;
height: 100%;
width: 100%;
overflow: hidden;
margin: 0 auto;
margin-top: 4px;
}
.canvas-wraper .title{
text-align: center;
}
/* 绘图区 end */
/* 图片展示区 start */
.imgbase{
border: 1px solid;
margin-top: 1rem;
}
.imgbase .title{
text-align: center;
}
/* 图片展示区 end */
</style>