当我们在特定的场景可能需要进行元素之间的连线,这一篇主要讲如何实现带箭头的折线。
折线部分 new fabric.Path
官网地址
其实折线也算是不规则图形,只不过没有背景颜色fill。
先不加两端元素(圆圈和箭头)。
示例(有背景色):
/**
* 绘制路径 折线
* @param coords 路径的起点和转折点
*/
function addPathLine(coords = 'M 100 100 L 300 100 L 300 300') {
let line = new fabric.Path(coords, {
fill: 'red',
stroke: 'rgba(0,195,244)',
strokeWidth: 4,
objectCaching: false,
// 禁止选中
selectable: false,
hasBorders: false, //当设置为 `false` 时,对象的控制边界不会被渲染
hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
hoverCursor: 'default',
name: `${ElCircleEnum.PATH_LINE}`,
});
canvasContainer.add(line);
}
如果 coords = ‘M 100 100 L 300 100 L 300 300 Z’,在最后面加一个Z,则会使不规则图形闭合,如下图所示:
接下来为了数据查看和存储方便我们用数组来存放coords。
/**
* 获取路径字符串
* @param coords
*/
function getPathByCoords(coords) {
let str = '';
(coords || []).forEach((cItem, cIndex) => {
if (cIndex == 0) {
str = cItem.join(' ');
} else {
str = str + ' ' + cItem.join(' ');
}
});
return str;
}
/**
* 绘制路径 折线
* @param coords 路径的起点和转折点
*/
function addPathLine(
coords = [
['M', 100, 100],
['L', 300, 100],
['L', 300, 300],
]
) {
let line = new fabric.Path(getPathByCoords(coords), {
fill: '',
stroke: 'rgba(0,195,244)',
strokeWidth: 4,
objectCaching: false,
// 禁止选中
selectable: false,
hasBorders: false, //当设置为 `false` 时,对象的控制边界不会被渲染
hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
hoverCursor: 'default',
name: `${ElCircleEnum.PATH_LINE}`,
});
canvasContainer.add(line);
}
起点圆圈部分 new fabric.Circle
// 拖拽点大小
const DROG_CIRCLE = 8;
/**
* 绘制
* @param left
* @param top
* @param fill
*/
function makeCurveCircle(left, top, fill = '#13416e') {
return new fabric.Circle({
left: left,
top: top,
strokeWidth: 2,
radius: DROG_CIRCLE - 1,
// fill: '#fff',
// stroke: '#666',
fill,
stroke: '#96cbf5',
// hasBorders: true, // 它指定是否使边框可见
hasControls: false, // 它指定是否禁用控件
opacity: 0.8,
centeredRotation: true,
originX: 'center',
originY: 'center',
});
}
/**
* 绘制路径 折线
* @param coords 路径的起点和转折点
*/
function addPathLine(
coords = [
['M', 100, 100],
['L', 300, 100],
['L', 300, 300],
]
) {
...
// 起点的拖拽图标
const cStart = makeCurveCircle(coords[0][1], coords[0][2]);
cStart.name = `${ElCircleEnum.START_CIRCLE}`;
canvasContainer.add(cStart);
}
终点三角形部分 new fabric.Triangle
/**
* 绘制箭头
*/
function drawEndArrow(left, top) {
return new fabric.Triangle({
width: DROG_CIRCLE * 2,
height: DROG_CIRCLE * 2,
left: left,
top: top,
fill: 'rgba(0,195,244)',
opacity: 1,
centeredRotation: true,
originX: 'center',
originY: 'center',
// 禁止选中
selectable: true,
hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
});
}
/**
* 绘制路径 折线
* @param coords 路径的起点和转折点
*/
function addPathLine(
coords = [
['M', 100, 100],
['L', 300, 100],
['L', 300, 300],
]
) {
...
// 终点的拖拽图标
const cEnd = drawEndArrow(
coords[coords.length - 1][1],
coords[coords.length - 1][2]
);
cEnd.name = `${ElCircleEnum.END_CIRCLE}`;
canvasContainer.add(cEnd);
}
虽然三角形绘制成功,但是作为箭头肯定是指向出口的嘛!这显然不对,所以我们还需要控制一下箭头的旋转方向。我们可以根据线段路径的最后两个点坐标来控制旋转方向,因为两点成线,并且折线只会有两种方向:
- 垂直方向(x相同):如果最后一个点的y大于倒数第二个点的y坐标,则旋转180°(C),反之旋转0°(D)
- 水平方向(y相同):如果最后一个点的x大于倒数第二个点的x坐标,则旋转90°(A),反之旋转270°(B)
示例:
/**
* 根据绘制的路径判断终点箭头的旋转方向
* @param coords
*/
function getTriangleAngleByCoords(coords) {
if (coords.length < 2) return 0;
// 最后一个点
const x1 = coords[coords.length - 1][1];
const y1 = coords[coords.length - 1][2];
// 倒数第二个点
const x2 = coords[coords.length - 2][1];
const y2 = coords[coords.length - 2][2];
let angle = 0;
if (x1 < x2 && checkSame(y1, y2)) {
angle = 270;
}
if (x1 > x2 && checkSame(y1, y2)) {
angle = 90;
}
if (y1 > y2 && checkSame(x1, x2)) {
angle = 180;
}
if (y1 < y2 && checkSame(x1, x2)) {
angle = 0;
}
return angle;
}
function checkSame(num1, num2) {
return num1.toFixed(6) == num2.toFixed(6);
}
/**
* 绘制箭头
*/
function drawEndArrow(left, top, coords) {
const angle = getTriangleAngleByCoords(coords);
return new fabric.Triangle({
width: DROG_CIRCLE * 2,
height: DROG_CIRCLE * 2,
left: left,
top: top,
fill: 'rgba(0,195,244)',
angle,
opacity: 1,
centeredRotation: true,
originX: 'center',
originY: 'center',
// 禁止选中
selectable: true,
hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
});
}
/**
* 绘制路径 折线
* @param coords 路径的起点和转折点
*/
function addPathLine(
coords = [
['M', 100, 100],
['L', 300, 100],
['L', 300, 300],
]
) {
...
// 终点的拖拽图标
const cEnd = drawEndArrow(
coords[coords.length - 1][1],
coords[coords.length - 1][2],
coords
);
cEnd.name = `${ElCircleEnum.END_CIRCLE}`;
canvasContainer.add(cEnd);
}