前言:
在图形编辑器类型的项目当中,通过键盘触发想要绘制的图形类型,然后通过鼠标在fabric画布上自由绘制你想需要的内容。从画基本的矩形、圆形、直线、文本、三角形、折线等功能中,可以扩展出“钢笔path贝塞尔路径”、“多图形组合”、图形合并、图形拆分、解析svg文件(符合要求的文件皆可)进行导入等较为复杂的功能等。
虽然上述介绍了很多各个不同的功能,但本篇写的内容仅限于文章标题范围!
其他提到的本文肯定不可能都写出来,实际写出来代码就太多了。但是所有的功能都离不开核心的基础地基,打好地基,扩展出对应的功能便轻而易举。
主要涉及功能:
功能对应的全局键盘快捷键、监听画布事件(鼠标按下、鼠标移动、鼠标松开)、初始化图形相关数据并添加进画布、更新画布、计算并更新图形坐标、画布框选功能启用/关闭;
相关要求:
- 通过界面按钮或键盘快捷键启用对应图形的绘画模式;(本文所使用的快捷键库若有了解的需要自行搜索我对应文章即可;)
- 监听fabric鼠标按下事件、移动事件、弹起事件;
- 在鼠标按下事件中创建图形并根据不同图形类型声明对应的初始数据。
- 在鼠标移动事件中实时更新对应图形的相关坐标。
- 在鼠标弹起事件中结束绘画,恢复相关数据初始值,并根据自身业务需求进行额外操作即可。
其他注意事项:绘画过程中按其他相关键盘快捷键则结束当前图形绘画。当然也不一定都是结束当前绘画执行新快捷键的功能,例如有辅助画正圆 正方的需求处理。所以这些都是根据自身业务需求进行定制功能,思维要灵活。
PS: 本文不对相关功能进行拆分,一个文件里展示完,自己写业务的时候进行相关拆分、封装即可;
<template>
<div class="cdie" id="cdie">
<canvas id="c" ref="canvas"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from "vue";
import { fabric } from "fabric";
import hotkeys from 'hotkeys-js';
window.fabric = fabric
let f = null
let canvas = ref();
let drawType;
function initHotkey() {
hotkeys('r', () => {
// 矩形
drawType = 'r' // 简单写个值,在业务里建议定义枚举类较好。
});
hotkeys('l', () => {
// 直线
drawType = 'l' // 简单写个值,在业务里建议定义枚举类较好。
});
hotkeys('c', () => {
// 圆形
drawType = 'c' // 简单写个值,在业务里建议定义枚举类较好。
});
}
onMounted(() => {
window.canvas = f = new fabric.Canvas(canvas.value, {
backgroundColor: "grey",
width: 1000,
height: 500,
});
initHotkey() // 声明图形绘画的启用快捷键
initDrawEvent(f) // 创建图形绘画相关事件;
});
function initDrawEvent(canvas) {
let shape: fabric.Object | null;
let startPoint: fabric.IPoint; // 记录初始坐标
canvas.on('mouse:down', (e) => {
if (e.target || !drawType) {
// 如果绘画点击在图片上,则不进行绘画
return;
}
if (!shape) {
f.selection = false;
startPoint = e.absolutePointer
switch (drawType) {
case 'r':
shape = new fabric.Rect({ //创建对应图形类型
left: startPoint.x,
top: startPoint.y,
width: 0,
height: 0,
fill: undefined,
stroke: 'red'
});
break;
case 'c':
shape = new fabric.Ellipse({
left: startPoint.x,
top: startPoint.y,
rx: 0,
ry: 0,
fill: undefined,
stroke: 'red'
});
break;
case 'l':
shape = new fabric.Line([startPoint.x, startPoint.y, startPoint.x, startPoint.y], {
fill: undefined,
stroke: 'red'
});
break;
default:
break;
}
if (shape) {
f.add(shape); //添加图形
f.requestRenderAll(); //刷新画布
}
}
window.selected = e?.target // 当点击选择到有可选图形时,会获得图形的数据。
}).on('mouse:move', (e: fabric.IEvent<MouseEvent>) => {
if (drawType && shape) {
const p = f.getPointer(e.e) || {
x: 0,
y: 0,
};
const minX = Math.min(p.x, startPoint.x);
const minY = Math.min(p.y, startPoint.y);
let w = Math.abs(p.x - startPoint.x);
let h = Math.abs(p.y - startPoint.y);
switch (drawType) {
case 'r':
shape.set({
left: minX,
top: minY,
width: w,
height: h,
});
break;
case 'c':
shape.set({
left: minX,
top: minY,
rx: w / 2,
ry: h / 2,
});
break;
case 'l':
let x1 = startPoint.x;
let y1 = startPoint.y;
let x2 = p.x;
let y2 = p.y;
console.log(startPoint, p);
shape.set({
x1,
y1,
x2,
y2,
});
break;
default:
break;
}
f.requestRenderAll();
}
}).on('mouse:up', (e) => {
if (drawType && shape) {
shape.setCoords(); // 更新图像坐标;
drawType = null
f.selection = true;
shape = null;
f.requestRenderAll();
}
})
}
</script>
<style scoped lang="less">
.cdie {
width: 100%;
text-align: center;
display: flex;
justify-content: center;
}
</style>