功能介绍
该功能是在fabricjs
画布上通过鼠标按下,拖动,点击选点,双击完成绘制来画一个带有折线箭头的功能,做这个功能主要是因为目前该类型的多数场景都只是支持直线箭头,所以我觉得有必要记录一下。
遇到的问题
- 如何画箭头折线
- 如何画折线末尾的三角形
- 如何回显
- ...等等
代码实现
<template>
<div>
<p style="color: red">
鼠标点击画布 移动鼠标 ,然后点击 再移动 再双击即可绘制折线
</p>
<div>
<button @click="save">保存数据</button>
</div>
<div style="display: flex; width: 100%; height: 100%">
<canvas id="canvas" style="border: 1px solid #ccc"></canvas>
<textarea v-if="content" v-model="content" cols="30" rows="10"></textarea>
</div>
</div>
</template>
<script setup>
import { fabric } from "fabric";
import { onMounted, ref } from "vue";
// 将原点坐标放到对象的中心
fabric.Object.prototype.originX = "center";
fabric.Object.prototype.originY = "center";
// 更改后 获取新的坐标点
function getPoints(obj) {
var matrix = [];
matrix = obj.calcTransformMatrix();
var translatedPoints = obj.get("points").map(function (p) {
return {
x: p.x - obj.pathOffset.x,
y: p.y - obj.pathOffset.y,
};
});
for (var i = 0; i < translatedPoints.length; i++) {
translatedPoints[i].x =
matrix[0] * translatedPoints[i].x +
matrix[2] * translatedPoints[i].y +
matrix[4];
translatedPoints[i].y =
matrix[1] * translatedPoints[i].x +
matrix[3] * translatedPoints[i].y +
matrix[5];
}
return translatedPoints;
}
// 获取角度
function getAngle(points) {
let p1 = points[points.length - 1];
let p2 = points[points.length - 2];
const xDiff = p1.x - p2.x;
const yDiff = p1.y - p2.y;
// 初始角度 我搞了个瘦长的箭头 先默认让箭头平躺下来
let angle = 90;
angle += (Math.atan2(yDiff, xDiff) * 180) / Math.PI;
console.log(angle);
return angle;
}
const content = ref(null);
let canvas;
onMounted(() => {
// 回显的数据
const data = [{"left":197.71481145862157,"top":246.5,"angle":0,"scaleX":1,"scaleY":1,"fill":"rgb(0,0,0)","points":[{"x":109.5,"y":146},{"x":93.5,"y":347},{"x":285.5,"y":331},{"x":290.5,"y":243}],"angle2":3.2519456003638822},{"left":508.5735325128169,"top":276,"angle":0,"scaleX":1,"scaleY":1,"fill":"rgb(0,0,0)","points":[{"x":449.5,"y":151},{"x":450.5,"y":333},{"x":404.5,"y":401},{"x":597.5,"y":326},{"x":601.5,"y":238}],"angle2":2.6025622024998114},{"left":432.6213452675692,"top":524.0556139716009,"angle":0,"scaleX":1,"scaleY":1,"fill":"rgb(0,0,0)","points":[{"x":250.5,"y":501},{"x":421.5,"y":562},{"x":511.5,"y":484},{"x":588.5,"y":541}],"angle2":126.5110561194953}]
canvas = new fabric.Canvas("canvas", {
selection: false,
width: 1000,
height: 1000,
});
data.map((v) => {
let points = v["points"];
let angle = v["angle2"];
let p1 = points[points.length - 1];
console.log(angle);
let polyline = new fabric.Polyline(points, {
stroke: "#000",
fill: "transparent",
});
// 注意 三角形的大小和初始旋转角度很重要
var triangle = new fabric.Triangle({
width: 20,
height: 50,
fill: "#000",
left: p1.x,
top: p1.y,
angle,
});
let group = new fabric.Group([polyline, triangle]);
canvas.add(group);
group.setControlsVisibility({
mtr: true,
bl: true,
br: true,
mb: false,
ml: false,
mr: false,
mt: false,
tl: true,
tr: true,
});
});
function drawPolyLineArrow(e, canvas) {
const currentPoint = e.absolutePointer;
let currentPolyline = new fabric.Polyline(
[
{ x: currentPoint.x, y: currentPoint.y },
{ x: currentPoint.x, y: currentPoint.y },
],
{
fill: "transparent",
stroke: "rgba(0, 0, 0, 0.2)",
objectCaching: false,
}
);
canvas.currentShape = currentPolyline;
canvas.add(currentPolyline);
}
let drawing = false;
canvas.on("mouse:down", function (opt) {
if (canvas.getActiveObject()) {
return;
}
const { x, y } = opt.pointer;
canvas.startX = x;
canvas.startY = y;
if (!drawing) {
// 如果是第一次
drawing = true;
drawPolyLineArrow(opt, canvas);
} else {
// 当没有松开鼠标 并且移动点击的时候
const currentPoint = opt.absolutePointer;
let points = canvas.currentShape.points;
points.push({
x: currentPoint.x,
y: currentPoint.y,
});
canvas.requestRenderAll();
}
});
// 移动鼠标事件
canvas.on("mouse:move", function (opt) {
if (drawing) {
if (canvas.currentShape) {
const currentPoint = opt.absolutePointer;
let points = canvas.currentShape.points;
points[points.length - 1].x = currentPoint.x;
points[points.length - 1].y = currentPoint.y;
canvas.requestRenderAll();
}
}
});
// 松开鼠标事件
canvas.on("mouse:up", function (opt) {
if (canvas.currentShape) {
return;
}
drawing = false;
canvas.currentShape = null;
});
canvas.on("mouse:dblclick", function (opt) {
if (canvas.currentShape) {
const currentPoint = opt.absolutePointer;
let points = canvas.currentShape.points;
console.log(points);
points[points.length - 1].x = currentPoint.x;
points[points.length - 1].y = currentPoint.y;
points.pop();
points.pop();
canvas.remove(canvas.currentShape);
console.log(points);
if (points.length > 1) {
let p1 = points[points.length - 1];
let angle = getAngle(points);
let polyline = new fabric.Polyline(points, {
stroke: "#000",
fill: "transparent",
});
// 注意 三角形的大小和初始旋转角度很重要
var triangle = new fabric.Triangle({
width: 20,
height: 50,
fill: "#000",
left: p1.x,
top: p1.y,
angle,
});
let group = new fabric.Group([polyline, triangle]);
// 由于目前拉伸四个边的控制 会导致一些问题( 主要是三角形的角度坐标会出毛病) ,所以这里我选择不让拉伸变形 只能等比例拉伸
group.setControlsVisibility({
mtr: true,
bl: true,
br: true,
mb: false,
ml: false,
mr: false,
mt: false,
tl: true,
tr: true,
});
canvas.add(group);
}
canvas.requestRenderAll();
canvas.currentShape = null;
drawing = false;
}
});
});
// 根据需要保存一些key到后台,当然你也可以自定义一些你的数据保存
const saveTypesMap = {
left: true,
top: true,
angle: true,
scaleX: true,
scaleY: true,
fill: true,
};
function save() {
let data = [];
canvas.forEachObject((obj) => {
let json = {};
for (const key in saveTypesMap) {
json[key] = obj[key];
}
let points = getPoints(obj.item(0));
let angle2 = obj.item(1).angle;
json["points"] = points;
json["angle2"] = angle2;
data.push(json);
});
content.value = JSON.stringify(data);
}
</script>