前言
Demo源码:https://gitee.com/k21vin/front-end-data-visualization
本文章所有的gif图片由于录屏软件问题,上面的鼠标位置都是错位显示的!!
一、Fabric.js简介
Fabric.js是一个对canvas进行封装的Javascript库,在原生canvas之上提供了交互式对象模型,通过简洁的api就可以在画布上进行丰富的操作。
它主要的功能包括在canvas上创建和填充图形,比如矩形、圆形、多边形;生成的图像自带缩放、旋转、拖拽等功能;还可以给图形填充渐变颜色;各个图形可以相互组合等等。
二、开始
1、引入Fabric.js
npm i fabric --save
2、在main.js中使用
// main.js
import fabric from "fabric"
Vue.use(fabric)
3、初始化画布
<template>
<canvas id="canvas"></canvas>
</template>
<script>
export default {
data () {
canvas: null, // 画布对象
},
mounted() {
this.canvas = new fabric.Canvas('canvas', {
width: 300,
height: 200
})
}
</script>
三、方法
canvas.add(object) // 添加对象
canvas.remove(object) // 删除对象
canvas.setWidth(width) // 设置canvas宽度
canvas.setHeight(height) // 设置canvas高度
canvas.setDimensions({width, height})// 一键设置宽高
canvas.getObjects() // 获取所有对象
canvas.getActiveObject() // 获取选中的对象
canvas.clear() // 清除画布中所有对象
canvas.renderAll() // 重绘
canvas.requestRenderAll() // 请求重新渲染
canvas.getZoom() // 获取画布当前缩放值
canvas.sendToBack(object) // 移到对象到最底层
canvas.viewportCenterObjectH(object) // 水平居中对象
canvas.viewportCenterObjectV(object) // 垂直居中对象
canvas.viewportCenterObject(object) // 垂直水平居中对象
canvas.fxCenterObjectH(object) // 动画水平居中对象
canvas.fxCenterObjectV(object) // 动画垂直居中对象
canvas.fxCenterObject(object) // 动画垂直水平居中对象
let canvasJsonData = JSON.stringify(canvas.toJSON()) // 将画布序列化成json数据
let canvasSvgData = canvas.toSVG() // 将画布序列化成svg数据
canvas.loadFromJSON(canvasJsonData) // 反序列化Json数据
四、事件
1、常用事件
mouse: down // 鼠标按下事件
mouse: move // 鼠标移动事件
mouse: up // 鼠标移动事件
mouse: over // 鼠标移入事件
mouse: out // 鼠标移出事件
mouse: dblclick // 鼠标双击事件
object: added // 对象被添加事件
object: removed // 对象被删除事件
object: modified // 对象被修改事件
object: rotating // 对象被旋转事件
object: scaling // 对象被缩放事件
object: moving // 对象被移动事件
2、事件绑定
canvas.on('mouse: wheel', (opt) => {
console.log(opt)
})
3、事件解绑
canvas.off('mouse: wheel')
五、canvas常用属性
canvas.selection = true // 画布是否可选中 默认为true、false:不可选中
canvas.selectionColor = 'transparent' // 画布鼠标框选时的背景色
canvas.selectionBorderColor = 'transparent'// 画布鼠标框选时的边框颜色
canvas.selectionLineWidth = 6 // 画布鼠标框选时的边框厚度
canvas.selectionDashArray = [30, 4, 10] // 画布鼠标框选时边框虚线规则
canvas.selectionFullyContained = true // 只选择完全包含在拖动选择矩形中的形状
canvas.backgroundColor = '#2E3136' // 画布背景色
canvas.hoverCursor = 'pointer' // 鼠标光标样式 default、pointer、move等
canvas.skipTargetFind = true // 整个画板元素不能被选中
canvas.fireRightClick = true // 启用右键,options.button的数字为3
canvas.stopContextMenu = true // 禁止默认右键菜单
Fabric.js 可以通过 viewportTransform 属性配置画布的视窗属性
canvas.viewportTransform[4] = 100
canvas.viewportTransform[5] = 100
viewportTransform 是一个数组,里面有6个元素,默认值是 [1, 0, 0, 1, 0, 0]。从下标0开始,它们分别代表:
[0]: 水平缩放(x轴方向)
[1]: 水平倾斜(x轴方向)
[2]: 垂直倾斜(y轴方向)
[3]: 垂直缩放(y轴方向)
[4]: 水平移动(x轴方向)
[5]: 垂直移动(y轴方向)
六、对象属性
1、基本属性
let circle = new fabric.Circle({
top: 100, // y坐标
left: 100, // x坐标
fill: '#17b978', // 填充色
radius: 50 // 半径
})
rect.top = 100 // y坐标
rect.left = 100 // x坐标
rect.width = 100 // 矩形宽度
rect.height = 100 // 矩形高度
circle.radius = 50 // 圆半径
rect.fill = '#17b978' // 填充色
rect.stroke = '#FE5332' // 线条颜色
rect.strokeWidth = 10 // 线条宽度
rect.strokeMiterLimit = index // 可以用来记录当前选中的rectList列表的索引!!!!!
2、扩展属性
circle.set({
hasBorders: false
}
circle.selectable = false // 控件不能被选择,不会被操作
circle.hasControls = false // 只能移动不能(编辑)操作
circle.hasBorders = false // 选中时,是否显示边,true:显示(默认)
circle.borderColor = 'red' // 选中时,边的颜色
circle.borderScaleFactor = 5 // 选中时,边的粗细
circle.borderDashArray = [20, 5, 10, 7] // 选中时,虚线边的规则
circle.transparentCorners = false // 选中时,角是否是空心 true:空心 false:实心
circle.cornerColor = "#a1de93", // 选中时,角的颜色
circle.cornerStrokeColor = 'pink' // 选中时,角的边框的颜色
circle.cornerStyle = 'circle' // 选中时,角的属性 rect:矩形(默认)、circle:圆形
circle.cornerSize = 20 // 选中时,角的大小为20
circle.cornerDashArray = [10, 2, 6] // 选中时,虚线角的规则
circle.selectionBackgroundColor = '#ffc300' // 选中时,选框的背景色
circle.padding = 20 // 选中时,选框离图形的距离
circle.borderOpacityWhenMoving = 0.6 // 当对象活动和移动时,对象控制边界的不透明度
triangle.perPixelTargetFind = true // 选择三角形空白位置的时候无法选中,false:可以选中(默认)
七、图层层级操作
canvas.bringToFront(rect) // 移到顶层
canvas.sendToBack(rect) // 移到底层
canvas.bringForward(rect) // 上移一层
canvas.sendBackwards(rect) // 下移一层
canvas.moveTo(0) // 移动到指定层
八、复制和粘贴
1、复制
handleCopy() {
if (!canvas.getActiveObject()) {
this.$message.warning('请先选择元素')
return
}
this.canvas.getActiveObject().clone(cloned => {
this.cloneObjects = cloned
})
}
2、粘贴
handlePaste() {
if (!this.cloneObjects) {
return this.$message.warning('还没复制过任何内容')
}
this.cloneObjects.clone(cloned => {
this.canvas.discardActiveObject() // 取消选择
// 设置新内容的坐标位置
cloned.set({
left: cloned.left + 10,
top: cloned.top + 10,
evented: true
})
if (cloned.type === 'activeSelection') { // 如果复制的是多个对象,则需要遍历克隆对象
cloned.canvas = this.canvas;
cloned.forEachObject(obj => {
this.canvas.add(obj)
})
cloned.setCoords()
} else {
this.canvas.add(cloned)
}
this.cloneObjects.top += 10
this.cloneObjects.left += 10
this.canvas.setActiveObject(cloned)
this.canvas.requestRenderAll()
})
}
九、锁定
Fabric对象可以添加一些属性进行锁定,例如静止水平移动、静止垂直移动,静止缩放等等
1、静止水平移动(lockMovementX)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ffde7d',
top: 20,
left: 20
})
rect.lockMovementX = true
canvas.add(rect)
2、静止垂直移动(lockMovementY)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ffde7d',
top: 20,
left: 20
})
rect.lockMovementY = true
3、静止旋转(lockRotation)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ff9a3c',
top: 60,
left: 160
})
rect.lockRotation = true
4、静止水平缩放(lockScalingX)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ffde7d',
top: 20,
left: 20
})
rect.lockScalingX = true
5、静止垂直缩放(lockScalingY)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#f95959',
top: 20,
left: 20
})
rect.lockScalingY = true
6、限制拖动区域
let boundingBox = new fabric.Rect({
top: 100,
left: 100,
width: 600,
height: 400,
fill: '#f95959',
selectable: false
})
let movingBox = new fabric.Rect({
top: 150,
left: 150,
width: 100,
height: 100,
fill: 'yellow',
hasBorders: false,
hasControls: false,
hoverCursor: 'move'
})
this.canvas.add(boundingBox);
this.canvas.add(movingBox);
this.canvas.on("object:moving", (opt) => {
let top = movingBox.top;
let left = movingBox.left;
let topBound = boundingBox.top;
let bottomBound = topBound + boundingBox.height;
let leftBound = boundingBox.left;
let rightBound = leftBound + boundingBox.width;
opt.target.left = Math.min(Math.max(left, leftBound), rightBound - movingBox.width)
opt.target.top = Math.min(Math.max(top, topBound), bottomBound - movingBox.height)
})
十、分组
Groups是Fabric最强大的功能之一,它可以将任意数量的Fabric对象组合在一起,形成一个小组,分组后,所有对象都可以一起移动、修改、缩放、旋转甚至更改其外观等
let group = new fabric.Group([circle, text], {
left: 100,
top: 100,
angle: -10
})
canvas.add(group)
group.item(0).set("fill","red");
group.item(1).set({
text:"trololo",
fill:"white"
})
分组时要记住的另一件事是对象的状态。例如,在与图像组成组时,需要确保这些图像已完全加载:
fabric.Image.fromURL(logo, img => {
let img1 = img.scale(0.3).set({left: 0, top: 0})
fabric.Image.fromURL(logo, img => {
let img2 = img.scale(0.3).set({left: 80, top: 0})
fabric.Image.fromURL(logo, img => {
let img3 = img.scale(0.3).set({left: 160, top: 0})
let group = new fabric.Group([img1, img2, img3], {
left: 10,
top: 400
})
canvas.add(group)
})
})
})
十一、动画
每个Fabric对象都有一个animate方法,该方法可以动画化该对象,animate(动画属性,动画的结束值,[动画的详细信息])
let rect = new fabric.Rect({
left: 100,
top: 100,
width: 100,
height: 100,
fill: 'red'
})
rect.animate("angle", 45, {
onChange: canvas.renderAll.bind(canvas)
})
canvas.add(rect)
第一个参数是要设置动画的属性。第二个参数是动画的结束值。如果矩形具有-15°的角度,并且我们传递了45,则动画将从-15°变为45°。第三个参数是一个可选对象,用于指定动画的详细信息–持续时间,回调,缓动等
rect.animate("angle", 45, {
from: 0, // 允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)
duration: 1000, // 默认为500(ms),可用于更改动画的持续时间
easing: fabric.util.ease.easeOutBounce, // 缓动功能 easeOutBounce、easeInCubic、easeOutCubic、easeInElastic、easeOutElastic、easeInBounce、easeOutExpo
onChange: canvas.renderAll.bind(canvas), // 在每次刷新时都会执行
onComplete: (e) => { console.log(e) } // 在动画结束时调用的回调
})
// 向右移动100px
rect.animate('left', '+=100', {
onChange: canvas.renderAll.bind(canvas)
})
// 逆时针旋转5度
rect.animate('angle', '-=5', {
onChange: canvas.renderAll.bind(canvas)
})
十二、图像滤镜
fabric.Image的每个实例都具有“ filters”属性,该属性是一个简单的过滤器数组。该阵列中的每个过滤器都是Fabric过滤器之一的实例。或您自己的自定义过滤器的实例。
let url = 'http://localhost:82/public/img/5.png' // 图片url
let base64Url = await this.imgUrlToBase64(url) // 图片base64 url
// 正常照片
fabric.Image.fromURL(url, img => {
img.scale(0.3)
canvas.add(img)
})
// 单个滤镜
fabric.Image.fromURL(base64Url, img => {
img.scale(0.3)
img.left = 300
// 添加滤镜
img.filters.push(new fabric.Image.filters.Grayscale())
// 图片加载完成之后,应用滤镜效果
img.applyFilters()
canvas.add(img)
})
// 叠加滤镜
fabric.Image.fromURL(base64Url, img => {
img.scale(0.3)
img.set({
left: 300,
top: 250,
})
img.filters.push(
new fabric.Image.filters.Grayscale(),
new fabric.Image.filters.Sepia(), //色偏
new fabric.Image.filters.Brightness({ brightness: 0.2 }) //亮度
)
img.applyFilters()
canvas.add(img)
})
(说明:这里图片可能会出错,放本地图片地址会报“Cannot read property ‘naturalWidth’ of null”的错误,直接放网络图片地址会报“Failed to execute ‘texImage2D’ on ‘WebGLRenderingContext‘: The image element contains cross–origin data, and may not be loaded.”的错误。解决方法就是将转为Base64格式)
十三、渐变
Fabric支持在所有对象上设置填充或描边属性的渐变,首先创建渐变,然后将其分配给填充或描边。
1、线性渐变
// 圆
let circle = new fabric.Circle({
left: 100,
top: 100,
radius: 50,
})
let gradient = new fabric.Gradient({
type: 'linear', // linear or radial
gradientUnits: 'pixels', // pixels or pencentage 像素 或者 百分比
coords: { x1: 0, y1: 0, x2: circle1.width, y2: 0 }, // 至少2个坐标对(x1,y1和x2,y2)将定义渐变在对象上的扩展方式
colorStops:[ // 定义渐变颜色的数组
{ offset: 0, color: 'red' },
{ offset: 0.2, color: 'orange' },
{ offset: 0.4, color: 'yellow' },
{ offset: 0.6, color: 'green' },
{ offset: 0.8, color: 'blue' },
{ offset: 1, color: 'purple' },
]
})
circle.set('fill', gradient);
canvas.add(circle)
2、径向渐变
let circle = new fabric.Circle({
left: 100,
top: 100,
radius: 50
})
let gradient = new fabric.Gradient({
type: 'radial',
coords: {
r1: 50,
r2: 0,
x1: 50,
y1: 50,
x2: 50,
y2: 50,
},
colorStops: [
{ offset: 0, color: '#fee140' },
{ offset: 1, color: '#fa709a' }
]
})
circle.set('fill', gradient);
canvas.add(circle)
十四、拖拽和缩放画布
1、拖拽画布
<script>
export default {
data() {
return {
lastPosX: 0, // 上次鼠标位置X坐标
lastPosY: 0, // 上次鼠标位置Y坐标
isDragging: false, // 是否可以拖拽画布
}
},
mounted() {
... // 初始化canvas
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:move', this.onMouseMove)
this.canvas.on('mouse:up', this.onMouseUp)
},
methods: {
// 监听鼠标按下事件
onMouseDown(opt) {
this.lastPosX = opt.e.clientX
this.lastPosY = opt.e.clientY
this.isDragging = true
},
// 监听鼠标移动事件
onMouseMove(opt) {
if (this.isDragging) {
this.canvas.viewportTransform[4] += opt.e.clientX - this.lastPosX
this.canvas.viewportTransform[5] += opt.e.clientY - this.lastPosY
this.canvas.requestRenderAll()
this.lastPosX = opt.e.clientX
this.lastPosY = opt.e.clientY
}
},
// 监听鼠标松开事件
onMouseUp(opt) {
if (this.isDragging) {
this.canvas.setViewportTransform(this.canvas.viewportTransform)
this.isDragging = false
}
}
}
}
</script>
2、以画布中心点为基准手动缩放
<template>
<el-tooltip content="放大" placement="bottom-start">
<span class="iconfont icon-fangda" @click="onManualScale(-100)"></span>
</el-tooltip>
<el-tooltip content="缩小" placement="bottom-start">
<span class="iconfont icon-suoxiao" @click="onManualScale(100)"></span>
</el-tooltip>
</template>
<script>
export default {
// 中心点缩放画布
onManualScale(delta) {
let zoom = canvas.getZoom() // 获取画布当前缩放值
zoom *= 0.999 ** delta
zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
canvas.zoomToPoint({ // 以画布中心点为基准缩放
x: this.canvasBoxWidth / 2, // canvasBoxWidth 画布宽度
y: this.canvasBoxHeight / 2 // canvasBoxHeight 画布高度
}, zoom)
}
}
</script>
3、以鼠标指针位置为基准缩放
this.canvas.on('mouse:wheel', this.onMouseWheel)
// 监听鼠标放大缩小事件
onMouseWheel(opt) {
let delta = opt.e.deltaY // 滚轮,向上滚一下是 -100,向下滚一下是 100
let zoom = this.canvas.getZoom() // 获取画布当前缩放值
zoom *= 0.999 ** delta
zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
this.canvas.zoomToPoint({ // 以鼠标指针位置为基准缩放
x: opt.e.offsetX,
y: opt.e.offsetY
}, zoom)
opt.e.preventDefault()
opt.e.stopPropagation()
}
十五、右键菜单删除
<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<div id="delMenu" ref="delMenu" v-show="isShowDelMenu" :style="delMenuStyle" @contextmenu.prevent="">
<el-button type="iconButton" icon="h-icon-delete" @click="handleDeleteMenu">删除</el-button>
</div>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
activeEle: null, // 上次选中元素
isShowDelMenu: false, // 是否显示删除弹窗
delMenuStyle: '' // 删除弹窗定位样式
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
fireRightClick: true, // 启用右键,button的数字为3
stopContextMenu: true, // 禁止默认右键菜单
})
this.canvas.on('mouse:down', this.onMouseDown)
},
// 监听鼠标按下事件
onMouseDown(opt) {
// 还原上次选中状态
if (this.activeEle) {
this.activeEle.set('fill', 'transparent')
this.canvas.renderAll()
}
this.activeEle = opt.target || null
// 按下鼠标右键
if (opt.button === 3) {
// 点击到非图片控件 显示删除弹窗和填充控件背景色
if (opt.target && opt.target.type !== 'image') {
this.activeEle.set('fill', 'rgba(100, 100, 255, 0.3)')
this.canvas.renderAll()
this.isShowDelMenu = true
this.$nextTick(() => {
this.delMenuStyle = this.getMenuStyle(this.$refs.delMenu, opt)
})
} else {
// 否则隐藏删除弹窗
this.hiddenMenu()
}
// 按下鼠标左键
} else {
this.hiddenMenu()
}
},
// 获取弹窗坐标
getMenuStyle(ele, opt) {
let menuWidth = ele.offsetWidth
let menuHeight = ele.offsetHeight
let pointX = opt.pointer.x + 2
let pointY = opt.pointer.y + 2
if (this.$refs.canvasBox.offsetWidth - pointX <= menuWidth) {
pointX -= menuWidth
}
if (this.$refs.canvasBox.offsetHeight - pointY <= menuHeight) {
pointY -= menuHeight
}
return `left: ${pointX}px; top: ${pointY}px;`
},
// 隐藏菜单
hiddenMenu() {
this.activeEle = null
this.isShowDelMenu = false
},
// 删除选中元素
handleDeleteMenu() {
this.canvas.remove(this.activeEle)
this.canvas.requestRenderAll()
this.hiddenMenu()
}
}
}
十六、自由绘画
1、开启绘图模式
let canvas = new fabric.Canvas('canvas', {
isDrawingMode: true // 开启绘图模式
})
canvas.freeDrawingBrush.color = '#11999e' // 设置画笔颜色
canvas.freeDrawingBrush.width = 10 // 设置画笔粗细
canvas.freeDrawingBrush.shadow = new fabric.Shadow({ // 设置画笔投影
blur: 10,
offsetX: 10,
offsetY: 10,
affectStroke: true,
color: '#30e3ca'
})
2、关闭绘图模式
canvas.isDrawingMode = false
十七、绘制背景图片
1、方式一:通过img元素添加
<img src="@/assets/images/logo.png" id="logo">
let img = document.getElementById('logo')
img.onload = () => {
let canvasImage = new fabric.Image(imgElement, {
left: 100, // 距离画布左侧距离
top: 100, // 距离画布顶部距离
width: 200, // 图片宽度
height: 200, // 图片高度
angle: 50, // 旋转
opacity: 1 // 透明度
})
canvas.add(canvasImage)
}
2、方式二:通过图片路径添加
let url = 'http://localhost:82/public/img/logo.png'
fabric.Image.fromURL(url, img => {
let canvasImage = img.set({
scaleX: 0.5,
scaleY: 0.5
})
canvas.add(canvasImage)
})
十八、绘制文本
Fabric也提供了文本的相关功能,Fabric文本允许以面向对象的方式处理文本,原生canvas方法,只允许在非常低的级别上填充或描边文本,通过实例化fabric.Text实例,我们就可以使用文本,就像我们将使用任何其他Fabric对象:移动它,缩放它,更改其属性等, 其次它提供比canvas给我们更丰富的功能,包括:
Multiline support // 支持多行
Text alignment // 文本对齐 Left、center、right
Text background // 文本背景 背景也遵循文本对齐
Text decoration // 文字装饰 下划线Underline、上划线overline、贯穿线strike-through
Line height // 行高 使用多行文字时出错
Char spacing // 字符间距 使文本更紧凑或间距更大
Subranges // 子范围 将颜色和属性应用于文本对象的子范围
Multibyte // 多字节 支持表情符号
On canvas editing // 交互式画布编辑 可以直接在canvas上键入文本
1、基础用法
let text = new fabric.Text('Hello World!', {
left: 40,
top: 10,
fontFamily: 'Comic Sans', // 字体
fontSize: 60, // 字号
fontWeight: 600, // 字体重量(粗细),normal、bold 或 数字(100、200、400、600、800)
fontStyle: 'normal', // 字体风格 正常 normal 或 斜体 italic
charSpacing: 100, // 字距
fill: 'red', // 字体颜色
cornerColor: 'pink', // 角的颜色(被选中时)
angle: 30, // 旋转
backgroundColor: '#ffd460', // 背景色
borderColor: 'yellowGreen', // 边框颜色(被选中时)
borderScaleFactor: 4, // 边框粗细(被选中时)
borderDashArray: [10, 4, 20], // 创建边框虚线
stroke: '#3f72af', // 文字描边颜色(蓝色)
strokeWidth: 2, // 文字描边粗细
textAlign: 'left', // 对齐方式:left 左对齐; right 右对齐; center 居中
opacity: 0.8, // 不透明度
// text: '雷猴', // 文字内容,会覆盖之前设置的值
selectable: true, // 能否被选中,默认true
shadow: 'rgba(0, 0, 0, 0.5) 5px 5px 5px', // 投影
})
canvas.add(text)
2、文本修饰
// 下划线
let underlineText = new fabric.Text("I am an undrline text", {
underline: true
})
canvas.add(underlineText)
// 贯穿线
let strokeThroughText = new fabric.Text("I am a stroke-through text", {
linethrough: true,
top: 40
})
canvas.add(strokeThroughText)
// 上划线
let overlineText = new fabric.Text("I am overline text", {
overline:true,
top: 80
})
canvas.add(overlineText)
3、可编辑文本
let IText = new fabric.IText('雷猴啊,双击打几个字试下~', {
fontFamily: 'Comic Sans'
})
canvas.add(IText)
十九、绘制线和路径
1、绘制直线
let line = new fabric.Line([0, 100, 100, 100], {
fill: 'green', // 填充色
stroke: 'green', // 笔触颜色
strokeWidth: 2, // 笔触宽度
});
canvas.add(line);
2、绘制虚线
在绘制直线的基础上添加属性strokeDashArray[a,b],表示每隔a个像素空b个像素。
let line = new fabric.Line([0, 100, 100, 100], {
fill: 'green', // 填充色
stroke: 'green', // 笔触颜色
strokeWidth: 2, // 笔触宽度
strokeDashArray:[3,1]
});
canvas.add(line);
3、绘制路径
let path = new fabric.Path('M 0 0 L 200 100 L 170 200 z')
path.set({
top: 120, // 距离容器顶部距离 120px
left: 120, // 距离容器左侧距离 120px
fill: 'hotpink', // 填充 亮粉色
opacity: 0.5, // 不透明度 50%
stroke: 'black', // 描边颜色 黑色
strokeWidth: 10 // 描边粗细 10px
})
上述代码第一行“M”代表“移动”命令,“M 0 0” 代表把画笔移动到(0, 0)点坐标。“L”代表“线”,“L 200 100 ”的意思是使用钢笔画一条线,从(0, 0)坐标画到(200, 100)坐标。“z” 代表让图形闭合路径。这样就画出了一个三角形。画好三角形后,我们可以用set( )方法对三角形的位置、颜色、角度、透明度等属性进行设置。
二十、自由绘制矩形
<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<el-button @click="handleActiveRect">绘制矩形</el-button>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
lastPoint: null, // 上次鼠标点位坐标
strokeColor: 'transparent', // 轮廓填充颜色
isActiveRect: false, // 是否激活绘制矩形
rectList: [] // 绘制的矩形列表
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
width: this.$refs.canvasBox.offsetWidth,
height: this.$refs.canvasBox.offsetHeight,
backgroundColor: '#2E3136',
selectionColor: 'transparent',
selectionBorderColor: 'transparent',
hoverCursor: 'default'
})
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:up', this.onMouseUp)
this.canvas.on('object:added', this.onObjectAdded)
},
// 监听鼠标按下事件
onMouseDown(opt) {
if (this.isActiveRect) {
this.lastPoint = opt.absolutePointer || null
this.strokeColor = '#00FF64'
}
},
// 监听鼠标松开事件
onMouseUp(opt) {
if (this.isActiveRect) {
this.drawRect(opt.absolutePointer)
}
},
// 绘制完成元素事件
onObjectAdded(opt) {
let target = opt.target
if (target.stroke === '#00FF64') {
this.isActiveRect && this.rectList.push(target)
}
},
// 绘制矩形
drawRect(pointer) {
if (!this.lastPoint || JSON.stringify(this.lastPoint) === JSON.stringify(pointer)) { // 点击事件,不生成矩形
return
}
let top = Math.min(this.lastPoint.y, pointer.y)
let left = Math.min(this.lastPoint.x, pointer.x)
let width = Math.abs(this.lastPoint.x - pointer.x)
let height = Math.abs(this.lastPoint.y - pointer.y)
let rect = new fabric.Rect({
top,
left,
width,
height,
fill: 'transparent',
stroke: this.strokeColor,
selectable: false
})
this.canvas.add(rect)
this.lastPoint = null
this.strokeColor = 'transparent'
},
// 激活绘制矩形
handleActiveRect() {
this.isActiveRect = !this.isActiveRect
if(this.isActiveRect) {
this.canvas.selectionBorderColor = '#00FF64'
}
}
}
}
二十一、自由绘制圆形
<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<el-button @click="handleActiveCircle">绘制圆形</el-button>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
canvasCircle: null,
downPoint: null,
strokeColor: 'transparent', // 轮廓填充颜色
isActiveCircle: false, // 是否激活绘制圆形
circleList: [] // 绘制的圆形列表
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
width: this.$refs.canvasBox.offsetWidth,
height: this.$refs.canvasBox.offsetHeight,
backgroundColor: '#2E3136',
selectionColor: 'transparent',
selectionBorderColor: 'transparent',
hoverCursor: 'default'
})
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:move', this.onMouseMove)
this.canvas.on('mouse:up', this.onMouseUp)
this.canvas.on('object:added', this.onObjectAdded)
},
// 监听鼠标按下事件
onMouseDown(opt) {
if (this.isActiveCircle) {
this.downPoint = opt.absolutePointer
this.strokeColor = '#00FF64'
this.canvasCircle = new fabric.Circle({
top: this.downPoint.y,
left: this.downPoint.x,
radius: 0,
fill: 'transparent',
stroke: this.strokeColor,
strokeWidth: 2,
selectable: false,
})
this.canvas.add(this.canvasCircle)
}
},
// 监听鼠标移动事件
onMouseMove(opt) {
if (this.isActiveCircle && this.canvasCircle) {
let radius = Math.min(Math.abs(this.downPoint.x - opt.absolutePointer.x), Math.abs(this.downPoint.y - opt.absolutePointer.y)) / 2
let top = opt.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - radius * 2
let left = opt.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - radius * 2
this.canvasCircle.set('radius', radius)
this.canvasCircle.set('top', top)
this.canvasCircle.set('left', left)
this.canvas.requestRenderAll()
}
},
// 监听鼠标松开事件
onMouseUp(opt) {
if (this.isActiveCircle) {
if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
this.canvas.remove(this.canvasCircle)
} else {
if (this.canvasCircle){
this.canvasCircle.set('stroke', this.strokeColor)
}
}
this.canvasCircle = null
}
},
// 绘制完成元素事件
onObjectAdded(opt) {
let target = opt.target
if (target.stroke === '#00FF64') {
this.isActiveCircle && this.circleList.push(target)
}
},
// 激活绘制圆形
handleActiveCircle() {
this.isActiveCircle = !this.isActiveCircle
if(this.isActiveCircle) {
this.canvas.selectionBorderColor = '#00FF64'
}
}
}
}
二十二、自由绘制椭圆形
<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<el-button @click="handleActiveEllipse">绘制椭圆形</el-button>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
canvasEllipse: null,
downPoint: null,
strokeColor: 'transparent', // 轮廓填充颜色
isActiveEllipse: false, // 是否激活绘制椭圆形
ellipseList: [] // 绘制的椭圆形列表
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
width: this.$refs.canvasBox.offsetWidth,
height: this.$refs.canvasBox.offsetHeight,
backgroundColor: '#2E3136',
selectionColor: 'transparent',
selectionBorderColor: 'transparent',
hoverCursor: 'default'
})
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:move', this.onMouseMove)
this.canvas.on('mouse:up', this.onMouseUp)
this.canvas.on('object:added', this.onObjectAdded)
},
// 监听鼠标按下事件
onMouseDown(opt) {
if (this.isActiveEllipse) {
this.downPoint = opt.absolutePointer
this.strokeColor = '#00FF64'
this.canvasEllipse = new fabric.Ellipse({
top: this.downPoint.y,
left: this.downPoint.x,
rx: 0,
ry: 0,
fill: 'transparent',
stroke: this.strokeColor,
strokeWidth: 2,
selectable: false,
})
this.canvas.add(this.canvasEllipse)
}
},
// 监听鼠标移动事件
onMouseMove(opt) {
if (this.isActiveEllipse && this.canvasEllipse) {
let rx = Math.abs(this.downPoint.x - opt.absolutePointer.x) / 2
let ry = Math.abs(this.downPoint.y - opt.absolutePointer.y) / 2
let top = opt.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - ry * 2
let left = opt.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - rx * 2
this.canvasEllipse.set('rx', rx)
this.canvasEllipse.set('ry', ry)
this.canvasEllipse.set('top', top)
this.canvasEllipse.set('left', left)
this.canvas.requestRenderAll()
}
},
// 监听鼠标松开事件
onMouseUp(opt) {
if (this.isActiveEllipse) {
if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
this.canvas.remove(this.canvasEllipse)
} else {
if (this.canvasEllipse){
this.canvasEllipse.set('stroke', this.strokeColor)
}
}
this.canvasEllipse = null
}
},
// 绘制完成元素事件
onObjectAdded(opt) {
let target = opt.target
if (target.stroke === '#00FF64') {
this.isActiveEllipse && this.ellipseList.push(target)
}
},
// 激活绘制椭圆形
handleActiveEllipse() {
this.isActiveEllipse = !this.isActiveEllipse
if(this.isActiveEllipse) {
this.canvas.selectionBorderColor = '#00FF64'
}
}
}
}
二十三、自由绘制三角形
<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<el-button @click="handleActiveTriangle">绘制三角形</el-button>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
canvasTriangle: null,
downPoint: null,
strokeColor: 'transparent', // 轮廓填充颜色
isActiveTriangle: false, // 是否激活绘制三角形
triangleList: [] // 绘制的三角形列表
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
width: this.$refs.canvasBox.offsetWidth,
height: this.$refs.canvasBox.offsetHeight,
backgroundColor: '#2E3136',
selectionColor: 'transparent',
selectionBorderColor: 'transparent',
hoverCursor: 'default'
})
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:move', this.onMouseMove)
this.canvas.on('mouse:up', this.onMouseUp)
this.canvas.on('object:added', this.onObjectAdded)
},
// 监听鼠标按下事件
onMouseDown(opt) {
if (this.isActiveTriangle) {
this.downPoint = opt.absolutePointer
this.strokeColor = '#00FF64'
this.canvasTriangle = new fabric.Triangle({
top: this.downPoint.y,
left: this.downPoint.x,
width: 0,
height: 0,
fill: 'transparent',
stroke: this.strokeColor,
strokeWidth: 2,
selectable: false,
})
this.canvas.add(this.canvasTriangle)
}
},
// 监听鼠标移动事件
onMouseMove(opt) {
if (this.isActiveTriangle && this.canvasTriangle) {
let width = Math.abs(this.downPoint.x - opt.absolutePointer.x)
let height = Math.abs(this.downPoint.y - opt.absolutePointer.y)
let top = opt.absolutePointer.y > this.downPoint.y ? this.downPoint.y : opt.absolutePointer.y
let left = opt.absolutePointer.x > this.downPoint.x ? this.downPoint.x : opt.absolutePointer.x
this.canvasTriangle.set('width', width)
this.canvasTriangle.set('height', height)
this.canvasTriangle.set('top', top)
this.canvasTriangle.set('left', left)
this.canvas.requestRenderAll()
}
},
// 监听鼠标松开事件
onMouseUp(opt) {
if (this.isActiveTriangle) {
if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
this.canvas.remove(this.canvasTriangle)
} else {
if (this.canvasTriangle){
this.canvasTriangle.set('stroke', this.strokeColor)
}
}
this.canvasTriangle = null
}
},
// 绘制完成元素事件
onObjectAdded(opt) {
let target = opt.target
if (target.stroke === '#00FF64') {
this.isActiveTriangle && this.triangleList.push(target)
}
},
// 激活绘制矩形
handleActiveTriangle() {
this.isActiveTriangle = !this.isActiveTriangle
if(this.isActiveTriangle) {
this.canvas.selectionBorderColor = '#00FF64'
}
}
}
}
二十四、自由绘制多边形
<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<el-button @click="handleActivePolygon">绘制多边形</el-button>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
canvasPolygon: null,
strokeColor: 'transparent', // 轮廓填充颜色
isActivePolygon: false, // 是否激活绘制多边形
polygonList: [] // 绘制的多边形列表
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
width: this.$refs.canvasBox.offsetWidth,
height: this.$refs.canvasBox.offsetHeight,
backgroundColor: '#2E3136',
selectionColor: 'transparent',
selectionBorderColor: 'transparent',
hoverCursor: 'default'
})
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:move', this.onMouseMove)
this.canvas.on('mouse:dblclick', this.onDblclick)
this.canvas.on('object:added', this.onObjectAdded)
},
// 监听鼠标按下事件
onMouseDown(opt) {
if (this.isActivePolygon) {
this.strokeColor = '#00FF64'
if (this.canvasPolygon === null) {
this.createPolygon(opt)
} else {
this.changeCurrentPolygon(opt)
}
}
},
// 监听鼠标移动事件
onMouseMove(opt) {
if (this.isActivePolygon && this.canvasPolygon) {
this.changePolygonBelt(opt)
}
},
// 鼠标双击事件
onDblclick(opt) {
this.finishPolygon(opt)
},
// 绘制完成元素事件
onObjectAdded(opt) {
let target = opt.target
if (target.stroke === '#00FF64') {
this.isActivePolygon && this.polygonList.push(target)
}
},
// 创建多边形
createPolygon(opt) {
this.canvasPolygon = new fabric.Polygon([
{ x: opt.absolutePointer.x, y: opt.absolutePointer.y },
{ x: opt.absolutePointer.x, y: opt.absolutePointer.y }
], {
fill: 'transparent',
stroke: this.strokeColor,
objectCaching: false
})
this.canvas.add(this.canvasPolygon)
},
// 修改当前正在创建的多边形
changeCurrentPolygon(opt) {
let points = this.canvasPolygon.points
points.push({
x: opt.absolutePointer.x,
y: opt.absolutePointer.y
})
this.canvas.requestRenderAll()
},
// 多边形橡皮带
changePolygonBelt(opt) {
let points = this.canvasPolygon.points
points[points.length - 1].x = opt.absolutePointer.x
points[points.length - 1].y = opt.absolutePointer.y
this.canvas.requestRenderAll()
},
// 完成多边形绘制
finishPolygon(opt) {
let points = this.canvasPolygon.points
points[points.length - 1].x = opt.absolutePointer.x
points[points.length - 1].y = opt.absolutePointer.y
points.pop()
points.pop()
this.canvas.remove(this.canvasPolygon)
if (points.length > 2) {
let polygon = new fabric.Polygon(points, {
stroke: this.strokeColor,
fill: 'transparent',
selectable: false
})
this.canvas.add(polygon)
} else {
this.$message.warning('标记框小于最小标定像素!')
}
this.canvasPolygon = null
this.canvas.requestRenderAll()
this.strokeColor = 'transparent'
},
// 激活绘制多边形
handleActivePolygon() {
this.isActivePolygon = !this.isActivePolygon
if(this.isActivePolygon) {
this.canvas.selectionBorderColor = '#00FF64'
}
}
}
}
原文地址:https://blog.csdn.net/qq_40007317/article/details/130064033
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_38446.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!