vue2+Fabric.js库的使用(5)--Fabric的画板项目搭建1
前言
本篇文章会开始进行图形的自由绘制,第二篇文章是绘制固定的图形,现在是在此基础上,进行自由的绘制,也是项目的开始阶段
提示:以下是本篇文章正文内容,下面案例可供参考
一、自由绘制原理说明
自由绘制图形需要先选中要绘制的图形,然后在画布中监听鼠标,根据鼠标去绘制选择的图形。其中需要分为两类:
一类是矩形、线段、三角形、圆形等。这类图形只需要监听鼠标按下的地方,然后鼠标松开的地方,即可进行绘制。
一类是连续的折线,这类图形需要监听鼠标按下的每个地方,然后鼠标双击或者鼠标右键的地方为终点,进行绘制。
下面会分别进行绘制
二、自由绘制图形
1.框架搭建
1.1首先把基础框架搭建出来
我采用了element-ui这个UI库进行界面搭建,可直接安装,并引入
安装:
npm i element-ui -S
在main.js中引入:
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
render: h => h(App),
}).$mount('#app')
因为是演示,我这里采用的是全局引入的方式
1.2然后把绘制的框架搭建起来
操作栏:
<!-- 操作栏 -->
<el-radio-group v-model="clickMenubarName" size="mini">
<el-radio border v-for="item in menubarFun" :key="item.id" @click="clickEvent(item.name)" :title="item.name" :label='item.name'>{{item.title}}</el-radio>
</el-radio-group>
通过循环渲染:
menubarFun: [
{
id: 1,
name: 'select',
title: '选择', //提示
},
{
id: 2,
name: 'drawing',
title: '自由绘画',
},
{
id: 3,
name: 'line',
title: '直线',
},
{
id: 4,
name: 'dotted',
title: '虚线',
},
{
id: 11,
name: 'arrow',
title: '箭头',
},
{
id:9,
name:'broken',
title:'折线',
},
{
id: 5,
name: 'rect',
title: '矩形',
},
{
id: 6,
name: 'circle',
title: '圆形',
},
{
id: 7,
name: 'ellipse',
title: '椭圆形',
},
{
id: 8,
name: 'triangle',
title: '三角形',
},
{
id: 10,
name: 'text',
title: '文本',
}
],
效果如下:
1.3当选择不同的图形之后,就可以执行不同的绘制操作
需要定义菜单的点击事件:
//菜单栏按钮点击事件
clickEvent(value) {
this.clickMenubarName = value
if (value == 'select') {
this.canvasObj.skipTargetFind = false // 允许选中
this.canvasObj.isDrawingMode = false //关闭自由绘画
} else if (value == 'drawing') {
this.canvasObj.selection = false // 去除框选
this.canvasObj.skipTargetFind = true // 不允许选中
this.canvasObj.isDrawingMode = true //开启自由绘画
} else {
this.canvasObj.selection = false // 去除框选
this.canvasObj.skipTargetFind = true // 不允许选中
this.canvasObj.isDrawingMode = false //关闭自由绘画
}
},
定义三类:
- 点击选择按钮:不进行绘制
- 点击自由绘制:可以在画布中进行自由的绘制
- 点击其它的图形:可以进行图形绘制
效果如下:
1.4监听鼠标的事件,进行不同图形的绘制
//创建图形
drawing() {
switch (this.clickMenubarName) {
case 'rect': //绘制矩形
break
case 'ellipse': //绘制椭圆形
break
case 'line': //绘制直线
break
case 'dotted': //绘制虚线
break
case 'circle': //绘制圆形
break
case 'triangle': //绘制三角形
break
case 'broken': //绘制折线
break;
case 'arrow': //绘制箭头
break
case 'text': //绘制文本
break
}
},
1.5图形绘制的步骤
除了折线和文本外,其它基础图形的绘制步骤都是一样的,如下:
- 鼠标选中图形
- 鼠标在画布中按下,按下的时候记住按下的坐标
- 鼠标按下在画布中移动,每次移动需要重新获取坐标点,并且重新绘制当前的图形
- 当鼠标松开的时候,记住松开的时候的坐标,根据按下时的坐标和结束时的坐标进行图形绘制
- 然后把绘制好的图形放在栈中存储
折线和文本的稍微复杂一点,后面介绍
下面先以矩形的绘制进行说明,后续的其他图形的框架类似,只是图形绘制的方法不一样
1.绘制矩形
按照上述的步骤进行绘制
1.鼠标选中图形
2.鼠标在画布中按下
按下之后,记住按下的坐标,同时调用绘制图形的方法,进行图形绘制
//鼠标按下
'mouse:down': opt => {
this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标
this.drawing() //绘制图形
},
绘制图形的方法如下:
//创建图形
drawing() {
switch (this.clickMenubarName) {
case 'rect': //绘制矩形
this.patternClass = new fabric.Rect({
top: this.downPoint.y, //创建对象的坐标
left: this.downPoint.x,
width: 0, //宽和高
height: 0,
fill: 'transparent', //填充颜色
stroke: this.borderColor, //线条颜色
strokeWidth: this.borderWidth //线条宽度
})
this.canvas.add(this.patternClass)
break
case 'ellipse': //绘制椭圆形
break
case 'line': //绘制直线
break
case 'dotted': //绘制虚线
break
case 'circle': //绘制圆形
break
case 'triangle': //绘制三角形
break
case 'broken': //绘制折线
break;
case 'arrow': //绘制箭头
break
case 'text': //绘制文本
break
}
},
3.鼠标在画布中移动
鼠标在移动的时候,需要不断获取鼠标的坐标,并且不断的重新绘制图形
//鼠标移动
'mouse:move': opt => {
this.patternFun(opt) //重绘图形
},
重绘图形的方法如下:
//重新绘制图形
patternFun(e) {
if (this.patternClass) {
switch (this.clickMenubarName) {
case 'rect':
let rectW = Math.abs(this.downPoint.x - e.absolutePointer.x)
let rectH = Math.abs(this.downPoint.y - e.absolutePointer.y)
// 设置尺寸和所在位置
this.patternClass.set('width', rectW)
this.patternClass.set('height', rectH)
this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
// 刷新一下画布
this.canvas.requestRenderAll()
break
case 'ellipse':
break
case 'line':
break
case 'arrow':
break
case 'dotted':
break
case 'circle':
break
case 'triangle':
break
case 'broken':
break
}
}
},
4.当鼠标松开的时候
鼠标松开之后,过滤掉一些杂的图形(即开始点和结束点一样的图形),然后把绘制好的图形放在栈中存储
//鼠标抬起
'mouse:up': opt => {
if(this.clickMenubarName != 'broken'){
if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
this.canvas.remove(this.patternClass)
} else {
//将当前对象压入栈中
this.backStack.push(this.patternClass)
}
//将创建的对象置空
this.patternClass = null
}
},
这样,矩形的自由绘制就完成了,其中backStack里面会存储所有绘制的图形对象,看下效果:
完整代码:
<template>
<div class="hello">
<!-- 操作栏 -->
<el-radio-group v-model="clickMenubarName" @change="clickEvent" size="mini">
<el-radio border v-for="item in menubarFun" :key="item.id" :title="item.name" :label='item.name'>{{item.title}}</el-radio>
</el-radio-group>
<br>
<!-- 画布元素 -->
<canvas width="1200" height="500" id="canvas-box" style="border: 1px solid #ccc;"></canvas>
</div>
</template>
<script>
import { fabric } from 'fabric'
export default {
name: 'HelloWorld',
data() {
return {
menubarFun: [
{
id: 1,
name: 'select',
title: '选择', //提示
},
{
id: 2,
name: 'drawing',
title: '自由绘画',
},
{
id: 3,
name: 'line',
title: '直线',
},
{
id: 4,
name: 'dotted',
title: '虚线',
},
{
id: 11,
name: 'arrow',
title: '箭头',
},
{
id:9,
name:'broken',
title:'折线',
},
{
id: 5,
name: 'rect',
title: '矩形',
},
{
id: 6,
name: 'circle',
title: '圆形',
},
{
id: 7,
name: 'ellipse',
title: '椭圆形',
},
{
id: 8,
name: 'triangle',
title: '三角形',
},
{
id: 10,
name: 'text',
title: '文本',
}
],
clickMenubarName: 'select', //当前点击的name
downPoint: {}, //鼠标按下的点位置
borderWidth: 4, //所有图形边框的宽度
borderColor: '#000000', //所有图形边框的颜色
backStack: [], //所有的绘画对象栈
patternClass: null, //鼠标按下绘制的对象
}
},
mounted(){
this.init()
},
methods:{
init(){
this.canvas = new fabric.Canvas('canvas-box') // 这里传入的是canvas的id
this.canvas.on({
//鼠标按下
'mouse:down': opt => {
this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标
this.drawing() //绘制图形
},
//鼠标移动
'mouse:move': opt => {
this.patternFun(opt) //重绘图形
},
//鼠标抬起
'mouse:up': opt => {
if(this.clickMenubarName != 'broken'){
if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
this.canvas.remove(this.patternClass)
} else {
//将当前对象压入栈中
this.backStack.push(this.patternClass)
}
//将创建的对象置空
this.patternClass = null
}
},
})
},
//创建图形
drawing() {
switch (this.clickMenubarName) {
case 'rect': //绘制矩形
this.patternClass = new fabric.Rect({
top: this.downPoint.y, //创建对象的坐标
left: this.downPoint.x,
width: 0, //宽和高
height: 0,
fill: 'transparent', //填充颜色
stroke: this.borderColor, //线条颜色
strokeWidth: this.borderWidth //线条宽度
})
this.canvas.add(this.patternClass)
break
case 'ellipse': //绘制椭圆形
break
case 'line': //绘制直线
break
case 'dotted': //绘制虚线
break
case 'circle': //绘制圆形
break
case 'triangle': //绘制三角形
break
case 'broken': //绘制折线
break;
case 'arrow': //绘制箭头
break
case 'text': //绘制文本
break
}
},
//重新绘制图形
patternFun(e) {
if (this.patternClass) {
switch (this.clickMenubarName) {
case 'rect':
let rectW = Math.abs(this.downPoint.x - e.absolutePointer.x)
let rectH = Math.abs(this.downPoint.y - e.absolutePointer.y)
// 设置尺寸和所在位置
this.patternClass.set('width', rectW)
this.patternClass.set('height', rectH)
this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
// 刷新一下画布
this.canvas.requestRenderAll()
break
case 'ellipse':
break
case 'line':
break
case 'arrow':
break
case 'dotted':
break
case 'circle':
break
case 'triangle':
break
case 'broken':
break
}
}
},
//菜单栏按钮点击事件
clickEvent(value) {
console.log(value)
this.clickMenubarName = value
if (value == 'select') {
this.canvas.skipTargetFind = false // 允许选中
this.canvas.isDrawingMode = false //关闭自由绘画
} else if (value == 'drawing') {
this.canvas.selection = false // 去除框选
this.canvas.skipTargetFind = true // 不允许选中
this.canvas.isDrawingMode = true //开启自由绘画
} else {
this.canvas.selection = false // 去除框选
this.canvas.skipTargetFind = true // 不允许选中
this.canvas.isDrawingMode = false //关闭自由绘画
}
},
},
}
</script>
<style scoped lang="less">
.hello{
width: 100%;
height: 100%;
display: flex;
flex-direction:column ;
justify-content:center;
align-items:center;
}
/deep/.el-radio{
margin: 0;
}
</style>
后续的基础图形绘制的框架都是这个框架,即鼠标按下调用绘制的方法,鼠标移动就重新绘制图形,鼠标松开之后把绘制的图形存入栈中(把图形存入栈中的目的是为了序列化和反序列化,即重新加载渲染或者导出),不同的是绘制方法不一样,然后重新绘制的方法不一样
3.绘制直线
绘制方法:
case 'line': //绘制直线
this.patternClass = new fabric.Line(
[
this.downPoint.x, //直线的开始坐标
this.downPoint.y,
this.downPoint.x, //直线的结束坐标
this.downPoint.y
],
{
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
}
)
this.canvas.add(this.patternClass)
break
重新渲染方法:
case 'line':
this.patternClass.set('x2', this.downPoint.x)
this.patternClass.set('y2', this.downPoint.y)
let newX = e.absolutePointer.x
let newY = e.absolutePointer.y
this.patternClass.set({ x1: newX, y1: newY });
this.canvas.requestRenderAll()
break
4.绘制虚线
绘制方法:
case 'dotted': //绘制虚线
this.patternClass = new fabric.Line(
[
this.downPoint.x, //虚线的开始坐标
this.downPoint.y,
this.downPoint.x, //第二个结束的坐标
this.downPoint.y
],
{
fill: 'transparent',
strokeDashArray: [10, 3], //设置为虚线,第一个值是实线的长度,第二个是虚线的长度
stroke: this.borderColor,
strokeWidth: this.borderWidth
}
)
this.canvas.add(this.patternClass)
break
重新渲染方法:
case 'dotted':
this.patternClass.set('x2', this.downPoint.x)
this.patternClass.set('y2', this.downPoint.y)
let new1X = e.absolutePointer.x
let new1Y = e.absolutePointer.y
this.patternClass.set({ x1: new1X, y1: new1Y });
this.canvas.requestRenderAll()
break
5.绘制箭头
绘制方法:
case 'arrow': //绘制箭头
let path = this.changePath(this.downPoint.x, this.downPoint.y, this.downPoint.x, this.downPoint.y, 17.5, 17.5)
this.patternClass = new fabric.Path(path, {
stroke: this.borderColor,
fill: 'transparent',
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
重新渲染方法:
case 'arrow':
//先移除当前的图形,在重新生成新的
this.canvas.remove(this.patternClass)
let path = this.changePath(this.downPoint.x, this.downPoint.y, e.absolutePointer.x, e.absolutePointer.y, 17.5, 17.5)
this.patternClass = new fabric.Path(path, {
stroke: this.borderColor,
fill: 'transparent',
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
this.canvas.requestRenderAll()
break
箭头这里还需要一个方法,即获取箭头的路径的方法——changePath()。这个方法如下:
//返回绘制箭头的路径值
changePath(fromX, fromY, toX, toY, theta, headlen) {
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 = ' 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
return path
},
6.绘制圆形
绘制方法:
case 'circle': //绘制圆形
this.patternClass = new fabric.Circle({
top: this.downPoint.y,
left: this.downPoint.x,
radius: 0, // 圆的半径
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
重新渲染方法:
case 'circle':
let circleX = Math.abs(this.downPoint.x - e.absolutePointer.x) / 2
let circleY = Math.abs(this.downPoint.y - e.absolutePointer.y) / 2
let circleTop = e.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - circleY * 2
let circleLeft = e.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - circleX * 2
this.patternClass.set('radius', Math.max(circleX, circleY))
this.patternClass.set('top', circleTop)
this.patternClass.set('left', circleLeft)
this.canvas.requestRenderAll()
break
7.绘制椭圆
绘制方法:
case 'ellipse': //绘制椭圆形
this.patternClass = new fabric.Ellipse({
top: this.downPoint.y,
left: this.downPoint.x,
rx: 0, //椭圆的两个中心点
ry: 0,
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
重新渲染方法:
case 'ellipse':
let rx = Math.abs(this.downPoint.x - e.absolutePointer.x) / 2
let ry = Math.abs(this.downPoint.y - e.absolutePointer.y) / 2
let top = e.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - ry * 2
let left = e.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - rx * 2
// 设置椭圆尺寸和所在位置
this.patternClass.set('rx', rx)
this.patternClass.set('ry', ry)
this.patternClass.set('top', top)
this.patternClass.set('left', left)
this.canvas.requestRenderAll()
break
8.绘制三角形
绘制方法:
case 'triangle': //绘制三角形
this.patternClass = new fabric.Triangle({
left: this.downPoint.x,
top: this.downPoint.y,
width: 0, //三角形底边的宽度
height: 0, //底边到顶部角的高度
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
重新渲染方法:
case 'triangle':
let triangleHeight = Math.abs(e.absolutePointer.y - this.downPoint.y)
this.patternClass.set('width', Math.sqrt(Math.pow(triangleHeight, 2) + Math.pow(triangleHeight / 2.0, 2)))
this.patternClass.set('height', triangleHeight)
this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
this.canvas.requestRenderAll()
break
完整代码
完整代码如下(示例):
<template>
<div class="hello">
<!-- 操作栏 -->
<el-radio-group v-model="clickMenubarName" @change="clickEvent" size="mini">
<el-radio border v-for="item in menubarFun" :key="item.id" :title="item.name" :label='item.name'>{{item.title}}</el-radio>
</el-radio-group>
<br>
<!-- 画布元素 -->
<canvas width="1200" height="500" id="canvas-box" style="border: 1px solid #ccc;"></canvas>
</div>
</template>
<script>
import { fabric } from 'fabric'
export default {
name: 'HelloWorld',
data() {
return {
menubarFun: [
{
id: 1,
name: 'select',
title: '选择', //提示
},
{
id: 2,
name: 'drawing',
title: '自由绘画',
},
{
id: 3,
name: 'line',
title: '直线',
},
{
id: 4,
name: 'dotted',
title: '虚线',
},
{
id: 11,
name: 'arrow',
title: '箭头',
},
{
id:9,
name:'broken',
title:'折线',
},
{
id: 5,
name: 'rect',
title: '矩形',
},
{
id: 6,
name: 'circle',
title: '圆形',
},
{
id: 7,
name: 'ellipse',
title: '椭圆形',
},
{
id: 8,
name: 'triangle',
title: '三角形',
},
{
id: 10,
name: 'text',
title: '文本',
}
],
clickMenubarName: 'select', //当前点击的name
downPoint: {}, //鼠标按下的点位置
borderWidth: 4, //所有图形边框的宽度
borderColor: '#000000', //所有图形边框的颜色
backStack: [], //所有的绘画对象栈
patternClass: null, //鼠标按下绘制的对象
}
},
mounted(){
this.init()
},
methods:{
init(){
this.canvas = new fabric.Canvas('canvas-box',{
targetFindTolerance: 10, //当元素以实际内容选择时,这个值越大越表面容易被选择到
perPixelTargetFind: true // 选择绘画对象时,以对象实际内容选择,而不是所处边界
}) // 这里传入的是canvas的id
this.canvas.on({
//鼠标按下
'mouse:down': opt => {
this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标
this.drawing() //绘制图形
},
//鼠标移动
'mouse:move': opt => {
this.patternFun(opt) //重绘图形
},
//鼠标抬起
'mouse:up': opt => {
if(this.clickMenubarName != 'broken'){
if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
this.canvas.remove(this.patternClass)
} else {
//将当前对象压入栈中
this.backStack.push(this.patternClass)
}
//将创建的对象置空
this.patternClass = null
}
},
})
},
//创建图形
drawing() {
switch (this.clickMenubarName) {
case 'rect': //绘制矩形
this.patternClass = new fabric.Rect({
top: this.downPoint.y, //创建对象的坐标
left: this.downPoint.x,
width: 0, //宽和高
height: 0,
fill: 'transparent', //填充颜色
stroke: this.borderColor, //线条颜色
strokeWidth: this.borderWidth //线条宽度
})
this.canvas.add(this.patternClass)
break
case 'ellipse': //绘制椭圆形
this.patternClass = new fabric.Ellipse({
top: this.downPoint.y,
left: this.downPoint.x,
rx: 0, //椭圆的两个中心点
ry: 0,
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
case 'line': //绘制直线
this.patternClass = new fabric.Line(
[
this.downPoint.x, //直线的开始坐标
this.downPoint.y,
this.downPoint.x, //直线的结束坐标
this.downPoint.y
],
{
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
}
)
this.canvas.add(this.patternClass)
break
case 'dotted': //绘制虚线
this.patternClass = new fabric.Line(
[
this.downPoint.x, //虚线的开始坐标
this.downPoint.y,
this.downPoint.x, //第二个结束的坐标
this.downPoint.y
],
{
fill: 'transparent',
strokeDashArray: [10, 3], //设置为虚线,第一个值是实线的长度,第二个是虚线的长度
stroke: this.borderColor,
strokeWidth: this.borderWidth
}
)
this.canvas.add(this.patternClass)
break
case 'circle': //绘制圆形
this.patternClass = new fabric.Circle({
top: this.downPoint.y,
left: this.downPoint.x,
radius: 0, // 圆的半径
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
case 'triangle': //绘制三角形
this.patternClass = new fabric.Triangle({
left: this.downPoint.x,
top: this.downPoint.y,
width: 0, //三角形底边的宽度
height: 0, //底边到顶部角的高度
fill: 'transparent',
stroke: this.borderColor,
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
case 'broken': //绘制折线
break;
case 'arrow': //绘制箭头
let path = this.changePath(this.downPoint.x, this.downPoint.y, this.downPoint.x, this.downPoint.y, 17.5, 17.5)
this.patternClass = new fabric.Path(path, {
stroke: this.borderColor,
fill: 'transparent',
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
break
case 'text': //绘制文本
break
}
},
//重新绘制图形
patternFun(e) {
if (this.patternClass) {
switch (this.clickMenubarName) {
case 'rect':
let rectW = Math.abs(this.downPoint.x - e.absolutePointer.x)
let rectH = Math.abs(this.downPoint.y - e.absolutePointer.y)
// 设置尺寸和所在位置
this.patternClass.set('width', rectW)
this.patternClass.set('height', rectH)
this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
// 刷新一下画布
this.canvas.requestRenderAll()
break
case 'ellipse':
let rx = Math.abs(this.downPoint.x - e.absolutePointer.x) / 2
let ry = Math.abs(this.downPoint.y - e.absolutePointer.y) / 2
let top = e.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - ry * 2
let left = e.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - rx * 2
// 设置椭圆尺寸和所在位置
this.patternClass.set('rx', rx)
this.patternClass.set('ry', ry)
this.patternClass.set('top', top)
this.patternClass.set('left', left)
this.canvas.requestRenderAll()
break
case 'line':
this.patternClass.set('x2', this.downPoint.x)
this.patternClass.set('y2', this.downPoint.y)
let newX = e.absolutePointer.x
let newY = e.absolutePointer.y
this.patternClass.set({ x1: newX, y1: newY });
this.canvas.requestRenderAll()
break
case 'arrow':
//先移除当前的图形,在重新生成新的
this.canvas.remove(this.patternClass)
let path = this.changePath(this.downPoint.x, this.downPoint.y, e.absolutePointer.x, e.absolutePointer.y, 17.5, 17.5)
this.patternClass = new fabric.Path(path, {
stroke: this.borderColor,
fill: 'transparent',
strokeWidth: this.borderWidth
})
this.canvas.add(this.patternClass)
this.canvas.requestRenderAll()
break
case 'dotted':
this.patternClass.set('x2', this.downPoint.x)
this.patternClass.set('y2', this.downPoint.y)
let new1X = e.absolutePointer.x
let new1Y = e.absolutePointer.y
this.patternClass.set({ x1: new1X, y1: new1Y });
this.canvas.requestRenderAll()
break
case 'circle':
let circleX = Math.abs(this.downPoint.x - e.absolutePointer.x) / 2
let circleY = Math.abs(this.downPoint.y - e.absolutePointer.y) / 2
let circleTop = e.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - circleY * 2
let circleLeft = e.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - circleX * 2
this.patternClass.set('radius', Math.max(circleX, circleY))
this.patternClass.set('top', circleTop)
this.patternClass.set('left', circleLeft)
this.canvas.requestRenderAll()
break
case 'triangle':
let triangleHeight = Math.abs(e.absolutePointer.y - this.downPoint.y)
this.patternClass.set('width', Math.sqrt(Math.pow(triangleHeight, 2) + Math.pow(triangleHeight / 2.0, 2)))
this.patternClass.set('height', triangleHeight)
this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
this.canvas.requestRenderAll()
break
case 'broken':
break
}
}
},
//返回绘制箭头的路径值
changePath(fromX, fromY, toX, toY, theta, headlen) {
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 = ' 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
return path
},
//菜单栏按钮点击事件
clickEvent(value) {
this.clickMenubarName = value
if (value == 'select') {
this.canvas.selection = true // 允许框选
this.canvas.selectionColor = 'rgba(100, 100, 255, 0.3)' // 选框填充色:半透明的蓝色
this.canvas.selectionBorderColor = 'rgba(255, 255, 255, 0.3)' // 选框边框颜色:半透明灰色
this.canvas.skipTargetFind = false // 允许选中
this.canvas.isDrawingMode = false //关闭自由绘画
} else if (value == 'drawing') {
this.canvas.selection = false // 去除框选
this.canvas.skipTargetFind = true // 不允许选中
this.canvas.isDrawingMode = true //开启自由绘画
} else {
this.canvas.selection = false // 去除框选
this.canvas.skipTargetFind = true // 不允许选中
this.canvas.isDrawingMode = false //关闭自由绘画
}
},
},
}
</script>
<style scoped lang="less">
.hello{
width: 100%;
height: 100%;
display: flex;
flex-direction:column ;
justify-content:center;
align-items:center;
}
/deep/.el-radio{
margin: 0;
}
</style>
效果如下:
总结
本文主要介绍了基本图形的绘制方法,可以通过鼠标在画布上点击自由进行绘制,而不是固定的绘制,下一章会继续介绍文本和折线的自由绘制方法。
更多推荐
所有评论(0)