背景
标注的几何,有时需要一些定制化的渲染样式,例如,线中间展示箭头,表示方向。本期教程教大家如何实现fabric几何定制化渲染。
带箭头的线
fabric提供了一些原生的几何,例如Point、Polyline、Polygon。同时提供了一些抽象的实体,如Object、Path。
如果使用原生的几何,可配置的样式是有限的。
比如Point的配置是:
{radius: 5,stroke: 'rgba(0,0,0,1)',strokeWidth: 2,fill: 'rgba(255,0,0,1)'
}
Polyline的配置是:
{stroke: 'rgba(0,0,0,1)',strokeWidth: 5
}
Polygon的配置是:
{fill: 'rgba(255,0,0,0,3)',strokeWidth: 1
}
可见这些线宽、线颜色、填充色等并不能实现箭头样式,需要对这些类进行扩展。
fabric提供了方法,可以扩展原生几何,代码如下:
export const NewPolyline = fabric.util.createClass(fabric.Polyline, {type: 'NewPolyline',initialize: function (points: any = [], options: any = {}) {this.callSuper('initialize', points, { ...options }); // 调用Polyline的初始化方法},_render: function (ctx: any) {// 自定义渲染,每次canvas.renderAll()都会触发该方法。this.callSuper('_render', ctx);}
}
此时我们得到了一个新的fabric几何类型:NewPolyline。其初始化参数和Polyline一致,points是点序,options里设置其样式。
而箭头样式,需要在_render方法里实现。_render方法可以拿到ctx,即canvas的实例,我们可以利用ctx进行各种绘制操作。
注意:在_render这个步骤,每个几何都有自己的坐标系,其坐标系原点是几何的外接矩形的中心点。
因此,我们每个坐标都需要减去当前几何的width/2和height/2,进行原点平移。
举个例子,比如我们有一条线,其参数如下:
{left: 10,top: 10,points: [{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 5}],
}
在坐标系中,如图,
left和top将要素的坐标系从O移动到了O',在此基础上,绘制折线[[0,0],[5,0],[5,5]]。
在渲染时,fabric又将坐标原点O'平移到外接矩形的中心点O''。
知道坐标系后,我们先来求线段的中点:
const points = this.get('points');
const width = this.get('width');
const height = this.get('height');for (let i = 0; i < points.length; i++) {const midX = (points[i].x + points[i + 1].x) / 2 - width / 2;const midY = (points[i].y + points[i + 1].y) / 2 - height / 2;console.log(midX, midY);
}// 结果:
// -2.5, -2.5
// 2.5, -2.5
// 2.5, 2.5
看懂上面的代码,你就可以以线段中心点为中心,画沿着线段的三角形了,代码如下:
for (let i = 0; i < points.length - 1; i++) {const midX = (points[i].x + points[i + 1].x) / 2 - width / 2;const midY = (points[i].y + points[i + 1].y) / 2 - height / 2;const rotate = Math.atan2(points[i + 1].y - points[i].y, points[i + 1].x - points[i].x);ctx.moveTo(midX, midY);const firstX = midX - (arrowWidth / 2) * Math.sin(rotate);const firstY = midY + (arrowWidth / 2) * Math.cos(rotate);ctx.lineTo(firstX, firstY);const secondX = midX + (arrowWidth / 2) * Math.sqrt(3) * Math.cos(rotate);const secondY = midY + (arrowWidth / 2) * Math.sqrt(3) * Math.sin(rotate);ctx.lineTo(secondX, secondY);const thirdX = midX + (arrowWidth / 2) * Math.sin(rotate);const thirdY = midY - (arrowWidth / 2) * Math.cos(rotate);ctx.lineTo(thirdX, thirdY);ctx.closePath();ctx.fill();}
效果图如下:
了解这个原理,你就可以利用canvas的绘制操作实现任何的自定义样式。
缩放控制线宽等宽
上一章我们讲到了,画布是可以拖拽和缩放的,本质上是修改canvas的transform。
在每次缩放后,canvas会调用renderAll方法,从而调用每个几何的_render方法。在_render内,我们需要重新计算strokeWidth:
const strokeWidth = 5;
const zoom = canvas.getZoom();
this.set({strokeWidth: strokeWidth / zoom
});
这样可以保证每次缩放后,线宽依然维持一个固定值。如果我们不修改线宽,则会被同样得缩放。
预告
下一章,我们详细聊一个极其隐蔽的问题:线居中渲染。