npm install fabric@4.6.0 --save
<div @contextmenu.prevent>
<div class="toolbar">
<input type="file" @change="handleImageUpload"/>
<el-color-picker
v-model="selectedColor"
@change="onColorChange"
show-alpha
style="margin-left: 10px;"
></el-color-picker>
<button @click="drawMode = 'line'; enableLineDrawMode()">画线</button>
<button @click="addCircle">圆形</button>
<button @click="addText">文字</button>
</div>
<div
v-if="contextMenu.visible"
:style="contextMenu.style"
class="context-menu"
@click="deleteSelectedObject"
>
删除
</div>
<canvas ref="canvas" width="800" height="600" style="border:1px solid #ccc;"></canvas>
<el-button @click="saveHandle">保存</el-button>
</div>
</template>
<script>
import {fabric} from 'fabric';
export default {
name: 'DrawBoard',
data() {
return {
canvas: null,
drawMode: null,
isDrawingLine: false,
currentLine: null,
selectedColor: '#000000', // 默认黑色
contextMenu: {
visible: false,
style: {
top: '0px',
left: '0px'
},
target: null // 当前右键选中的元素
}
};
},
mounted() {
this.canvas = new fabric.Canvas(this.$refs.canvas, {
fireRightClick: true, // 启用右键,button的数字为3
stopContextMenu: true, // 禁止默认右键菜单
selection: true,
preserveObjectStacking: true
});
this.canvas.on('mouse:down', (opt) => {
console.log('mouse:down', opt);
if (opt.e.button === 2) { // 鼠标右键
const target = opt.target;
if (target) {
this.contextMenu.target = target;
// 显示右键菜单
this.contextMenu.visible = true;
this.contextMenu.style = {
top: `${opt.e.clientY}px`,
left: `${opt.e.clientX}px`,
position: 'absolute'
};
} else {
this.contextMenu.visible = false;
}
// 阻止系统默认右键菜单
opt.e.preventDefault();
opt.e.stopPropagation();
} else {
this.contextMenu.visible = false;
}
});
this.reductionHandle()
},
methods: {
reductionHandle(){
let jsonString = '{"version":"4.6.0","objects":[{"type":"circle","version":"4.6.0","originX":"left","originY":"top","left":97,"top":106,"width":80,"height":80,"fill":"#000000","stroke":"#000","strokeWidth":2,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":3.49,"scaleY":3.49,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"radius":40,"startAngle":0,"endAngle":6.283185307179586},{"type":"textbox","version":"4.6.0","originX":"left","originY":"top","left":200,"top":200,"width":120,"height":27.12,"fill":"#000000","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"fontFamily":"Times New Roman","fontWeight":"normal","fontSize":24,"text":"请输入文字","underline":false,"overline":false,"linethrough":false,"textAlign":"left","fontStyle":"normal","lineHeight":1.16,"textBackgroundColor":"","charSpacing":0,"styles":{},"direction":"ltr","path":null,"pathStartOffset":0,"pathSide":"left","minWidth":20,"splitByGrapheme":false},{"type":"textbox","version":"4.6.0","originX":"left","originY":"top","left":474,"top":131,"width":120,"height":27.12,"fill":"#000000","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"fontFamily":"Times New Roman","fontWeight":"normal","fontSize":24,"text":"测试","underline":false,"overline":false,"linethrough":false,"textAlign":"left","fontStyle":"normal","lineHeight":1.16,"textBackgroundColor":"","charSpacing":0,"styles":{},"direction":"ltr","path":null,"pathStartOffset":0,"pathSide":"left","minWidth":20,"splitByGrapheme":false}]}'
this.canvas.loadFromJSON(jsonString, () => {
this.canvas.renderAll();
});
},
saveHandle() {
const jsonData = this.canvas.toJSON();
const jsonString = JSON.stringify(jsonData);
console.log(jsonString)
return
// 传给后端保存
axios.post('/api/save-canvas', {data: jsonString});
},
onColorChange(color) {
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
// 判断对象类型,设置颜色属性
if (activeObject.type === 'text' || activeObject.type === 'textbox') {
activeObject.set('fill', color);
} else if (activeObject.type === 'line' || activeObject.stroke) {
activeObject.set('stroke', color);
}
this.canvas.renderAll();
}
},
deleteSelectedObject() {
if (this.contextMenu.target) {
this.canvas.remove(this.contextMenu.target);
this.canvas.renderAll();
this.contextMenu.visible = false;
this.contextMenu.target = null;
}
},
/** 上传图片并添加到画布 */
handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (f) => {
fabric.Image.fromURL(f.target.result, (img) => {
img.set({left: 100, top: 100, scaleX: 0.5, scaleY: 0.5});
this.canvas.add(img);
});
};
reader.readAsDataURL(file);
},
/** 添加圆形 */
addCircle() {
const circle = new fabric.Circle({
left: 150,
top: 150,
radius: 40,
fill: this.selectedColor,
stroke: '#000',
strokeWidth: 2,
});
this.canvas.add(circle);
},
/** 添加文字 */
addText() {
const text = new fabric.Textbox('请输入文字', {
left: 200,
top: 200,
fontSize: 24,
fill: this.selectedColor,
editable: true,
});
this.canvas.add(text);
},
/** 启用画线模式 */
enableLineDrawMode() {
this.canvas.selection = false;
this.canvas.defaultCursor = 'crosshair';
this.isDrawingLine = true;
this.canvas.on('mouse:down', this.startLine);
this.canvas.on('mouse:move', this.drawLine);
this.canvas.on('mouse:up', this.endLine);
},
startLine(opt) {
if (!this.isDrawingLine) return;
const pointer = this.canvas.getPointer(opt.e);
const points = [pointer.x, pointer.y, pointer.x, pointer.y];
const line = new fabric.Line(points, {
strokeWidth: 2,
stroke: this.selectedColor,
selectable: false,
evented: false,
});
this.currentLine = line;
this.canvas.add(line);
},
drawLine(opt) {
if (!this.isDrawingLine || !this.currentLine) return;
const pointer = this.canvas.getPointer(opt.e);
this.currentLine.set({x2: pointer.x, y2: pointer.y});
this.canvas.renderAll();
},
endLine() {
if (!this.isDrawingLine) return;
this.currentLine.set({selectable: true, evented: true});
this.currentLine = null;
this.canvas.off('mouse:down', this.startLine);
this.canvas.off('mouse:move', this.drawLine);
this.canvas.off('mouse:up', this.endLine);
this.canvas.selection = true;
this.canvas.defaultCursor = 'default';
this.isDrawingLine = false;
},
}
};
</script>
<style scoped>
.toolbar {
margin-bottom: 10px;
}
button {
margin-right: 5px;
}
.context-menu {
position: absolute;
background: white;
border: 1px solid #ccc;
z-index: 9999;
padding: 5px 10px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
</style>
<template>
<div>
<div class="toolbar">
<input type="file" @change="handleImageUpload" />
<button @click="drawMode = 'line'; enableLineDrawMode()">画线</button>
<button @click="addCircle">圆形</button>
<button @click="addText">文字</button>
</div>
<canvas ref="canvas" width="800" height="600" style="border:1px solid #ccc;"></canvas>
</div>
</template>
<script>
import { fabric } from 'fabric';
export default {
name: 'DrawBoard',
data() {
return {
canvas: null,
drawMode: null,
isDrawingLine: false,
currentLine: null,
};
},
mounted() {
this.canvas = new fabric.Canvas(this.$refs.canvas, {
selection: true,
preserveObjectStacking: true
});
},
methods: {
/** 上传图片并添加到画布 */
handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (f) => {
fabric.Image.fromURL(f.target.result, (img) => {
img.set({ left: 100, top: 100, scaleX: 0.5, scaleY: 0.5 });
this.canvas.add(img);
});
};
reader.readAsDataURL(file);
},
/** 添加圆形 */
addCircle() {
const circle = new fabric.Circle({
left: 150,
top: 150,
radius: 40,
fill: 'rgba(0,0,255,0.5)',
stroke: '#000',
strokeWidth: 2,
});
this.canvas.add(circle);
},
/** 添加文字 */
addText() {
const text = new fabric.Textbox('请输入文字', {
left: 200,
top: 200,
fontSize: 24,
fill: '#333',
editable: true,
});
this.canvas.add(text);
},
/** 启用画线模式 */
enableLineDrawMode() {
this.canvas.selection = false;
this.canvas.defaultCursor = 'crosshair';
this.isDrawingLine = true;
this.canvas.on('mouse:down', this.startLine);
this.canvas.on('mouse:move', this.drawLine);
this.canvas.on('mouse:up', this.endLine);
},
startLine(opt) {
if (!this.isDrawingLine) return;
const pointer = this.canvas.getPointer(opt.e);
const points = [pointer.x, pointer.y, pointer.x, pointer.y];
const line = new fabric.Line(points, {
strokeWidth: 2,
stroke: 'red',
selectable: false,
evented: false,
});
this.currentLine = line;
this.canvas.add(line);
},
drawLine(opt) {
if (!this.isDrawingLine || !this.currentLine) return;
const pointer = this.canvas.getPointer(opt.e);
this.currentLine.set({ x2: pointer.x, y2: pointer.y });
this.canvas.renderAll();
},
endLine() {
if (!this.isDrawingLine) return;
this.currentLine.set({ selectable: true, evented: true });
this.currentLine = null;
this.canvas.off('mouse:down', this.startLine);
this.canvas.off('mouse:move', this.drawLine);
this.canvas.off('mouse:up', this.endLine);
this.canvas.selection = true;
this.canvas.defaultCursor = 'default';
this.isDrawingLine = false;
},
}
};
</script>
<style scoped>
.toolbar {
margin-bottom: 10px;
}
button {
margin-right: 5px;
}
</style>