前言
上文进行了基础图形的自由绘制,本文会基于文本和折线进行自由的绘制,本文是基于上文进行的,相当于补充。
提示:以下是本篇文章正文内容,下面案例可供参考
一、文本的绘制
文本的绘制相对比较简单,只需要监测鼠标按下即可,即鼠标按下就可以进行文本输出了,再把输入的文本存入栈中就可以了
//鼠标按下
'mouse:down': opt => {
this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标
//对文字对象的判断 判断是编辑还是新增
this.isTextObj(opt)
this.drawing() //绘制图形
},
需要定义输入文本的方法 isTextObj(),如下:
//判断上次的文字对象内容和编辑状态
isTextObj(opt) {
if (this.textObj !== '') {
if (this.textObj.isEditing === true) {
this.textObj.exitEditing()
//将文字对象存在栈中
this.backStack.push(this.textObj)
}
if (this.textObj.text === '') {
this.canvas.remove(this.textObj) //空文本删除
this.textObj = ''
}
}
//判断当前点击的对象是不是文字
if (opt.target) {
this.textObj = opt.target
}
},
这样处理之后还有个问题,就是会出现不灵敏,还需要在鼠标抬起的时候进行过滤判断:
if (!this.patternClass || this.clickMenubarName == 'text') {
//将自由绘画的对象存在栈中
if (!this.patternClass && this.canvas.selection === false) {
//比较对象长度是否发生变化,是否有新增
let getObj = this.canvas.getObjects()
if (this.canvasObjLength !== getObj.length) {
this.canvasObjLength = getObj.length
//将最新的自由绘画对象存储在back栈中
this.backStack.push(getObj[getObj.length - 1])
}
}
this.patternClass = null
return
}
效果就比较好了:
二、折线的绘制
折线的绘制相对是复杂一点的:
首先是鼠标按下,鼠标按下需要判断是否是第一次按下,如果是第一次按下,则相当于是重新开始绘制,需要把之前绘制的点位清零,如果不是第一次绘制,则是把当前按下的点位存储并进行渲染
然后是鼠标移动,鼠标移动的时候,需要不断的根据鼠标的位置重绘图形
然后是双击,鼠标双击的时候,需要停止绘制,表示这次的绘制完成
然后鼠标抬起这个是不需要做任何事情的
下面分别编写代码:
1、鼠标按下的时候
鼠标按下,需要判断是不是第一次绘制,然后做出不同的反应
if(this.clickMenubarName == 'broken'){
if(this.patternClass === null){
this.drawing()
}else{
this.patternFun(opt)
}
}
根据patternClass 是否为空判断,如果是第一次绘制,则开始绘制;如果不是第一次绘制,则需要加载绘制【这个方法和其它图形的重新渲染的放在一起的】
开始绘制的代码如下:
case 'broken': //绘制折线
const currentPoint = this.downPoint
this.patternClass = new fabric.Polyline(
[
{ x: currentPoint.x, y: currentPoint.y },
{ x: currentPoint.x, y: currentPoint.y }
],
{
fill: 'transparent',
stroke: this.borderColor,
strokeWidth:this.borderWidth,
objectCaching: false
}
)
this.canvas.add(this.patternClass)
break;
加载绘制的代码如下:
case 'broken':
//按下的时候只是修改最后一个节点的值
const currentPoint = e.absolutePointer
let points = this.patternClass.points
points.push({
x: currentPoint.x,
y: currentPoint.y
})
this.canvas.requestRenderAll()
break
2、鼠标移动的时候
移动的时候,需要重新渲染图形,根据鼠标的位置
//鼠标移动
'mouse:move': opt => {
//图案绘制
if(this.clickMenubarName != 'broken'){
this.patternFun(opt)
}
if(this.clickMenubarName == 'broken'){
this.changePolylineBelt(opt)
}
},
移动执行的方法changePolylineBelt()如下:
//折线移动
changePolylineBelt(e){
const currentPoint = e.absolutePointer
let points = this.patternClass.points
let newX = currentPoint.x
let newY = currentPoint.y
points[points.length - 1].x = newX
points[points.length - 1].y = newY
this.canvas.requestRenderAll()
},
3、鼠标双击的时候
鼠标双击的时候,执行额按成绘制的方法
//鼠标双击
'mouse:dblclick': opt => {
this.finishPolyline(opt)
},
完成绘制的方法finishPolyline()如下:
// 完成折线绘制
finishPolyline(e){
const currentPoint = e.absolutePointer
let points = this.patternClass.points
points[points.length - 1].x = currentPoint.x
points[points.length - 1].y = currentPoint.y
points.pop()
points.pop()
// 按需添加自闭合代码
// if (points[0].x != points[points.length - 1].x && points[0].y != points[points.length - 1].y) {
// changepatternClass({ absolutePointer: { x: points[0].x, y: points[0].y } })
// }
this.canvas.remove(this.patternClass)
if (points.length > 1) {
this.patternClass = null
this.patternClass = new fabric.Polyline(points, {
fill: 'transparent',
stroke: this.borderColor,
strokeWidth:this.borderWidth,
objectCaching: false
})
this.canvas.add(this.patternClass)
}
this.patternClass.style=this.clickMenubarName
//将当前对象压入栈中
this.backStack.push(this.patternClass)
this.patternClass = null
this.canvas.requestRenderAll()
},
这样,折线的绘制就完成了,效果如下:
完整代码
图形绘制到这里就结束了,完整的代码如下:
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.isTextObj(opt)
this.drawing() //绘制图形
if(this.clickMenubarName == 'broken'){
if(this.patternClass === null){
this.drawing()
}else{
this.patternFun(opt)
}
}
},
//鼠标移动
'mouse:move': opt => {
//图案绘制
if(this.clickMenubarName != 'broken'){
this.patternFun(opt)
}
if(this.clickMenubarName == 'broken'){
this.changePolylineBelt(opt)
}
},
//鼠标抬起
'mouse:up': opt => {
if (!this.patternClass || this.clickMenubarName == 'text') {
//将自由绘画的对象存在栈中
if (!this.patternClass && this.canvas.selection === false) {
//比较对象长度是否发生变化,是否有新增
let getObj = this.canvas.getObjects()
if (this.canvasObjLength !== getObj.length) {
this.canvasObjLength = getObj.length
//将最新的自由绘画对象存储在back栈中
this.backStack.push(getObj[getObj.length - 1])
}
}
this.patternClass = null
return
}
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
}
},
//鼠标双击
'mouse:dblclick': opt => {
this.finishPolyline(opt)
},
})
},
//创建图形
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': //绘制折线
const currentPoint = this.downPoint
this.patternClass = new fabric.Polyline(
[
{ x: currentPoint.x, y: currentPoint.y },
{ x: currentPoint.x, y: currentPoint.y }
],
{
fill: 'transparent',
stroke: this.borderColor,
strokeWidth:this.borderWidth,
objectCaching: false
}
)
this.canvas.add(this.patternClass)
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': //绘制文本
this.patternClass = new fabric.Textbox('', {
left: this.downPoint.x,
top: this.downPoint.y,
fontSize: 20,
hasBorders: true, //修改的时候,是否显示文字所在区域
width: 450,
splitByGrapheme: true, //超过宽度自动换行
fill: this.borderColor, //字体颜色
borderColor: 'rgb(102,153,255,0.35)' //当元素被拖动或输入内容时的边框
})
this.canvas.add(this.patternClass)
this.textObj = this.patternClass
this.patternClass.enterEditing()
break
}
},
//判断上次的文字对象内容和编辑状态
isTextObj(opt) {
if (this.textObj !== '') {
if (this.textObj.isEditing === true) {
this.textObj.exitEditing()
//将文字对象存在栈中
this.backStack.push(this.textObj)
}
if (this.textObj.text === '') {
this.canvas.remove(this.textObj) //空文本删除
this.textObj = ''
}
}
//判断当前点击的对象是不是文字
if (opt.target) {
this.textObj = opt.target
}
},
//重新绘制图形
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':
//按下的时候只是修改最后一个节点的值
const currentPoint = e.absolutePointer
let points = this.patternClass.points
points.push({
x: currentPoint.x,
y: currentPoint.y
})
this.canvas.requestRenderAll()
break
}
}
},
//折线移动
changePolylineBelt(e){
if(this.patternClass){
const currentPoint = e.absolutePointer
let points = this.patternClass.points
let newX = currentPoint.x
let newY = currentPoint.y
points[points.length - 1].x = newX
points[points.length - 1].y = newY
this.canvas.requestRenderAll()
}
},
// 完成折线绘制
finishPolyline(e){
const currentPoint = e.absolutePointer
let points = this.patternClass.points
points[points.length - 1].x = currentPoint.x
points[points.length - 1].y = currentPoint.y
points.pop()
points.pop()
// 按需添加自闭合代码
// if (points[0].x != points[points.length - 1].x && points[0].y != points[points.length - 1].y) {
// changepatternClass({ absolutePointer: { x: points[0].x, y: points[0].y } })
// }
this.canvas.remove(this.patternClass)
if (points.length > 1) {
this.patternClass = null
this.patternClass = new fabric.Polyline(points, {
fill: 'transparent',
stroke: this.borderColor,
strokeWidth:this.borderWidth,
objectCaching: false
})
this.canvas.add(this.patternClass)
}
this.patternClass.style=this.clickMenubarName
//将当前对象压入栈中
this.backStack.push(this.patternClass)
this.patternClass = null
this.canvas.requestRenderAll()
},
//返回绘制箭头的路径值
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 //关闭自由绘画
}
},
},
总结
到这里,基本图形的绘制就完成了,但是目前看来,还是不完善的,画布的操作没有加上,辅助绘制的一些操作也没有,后续会继续进行完善