说明
fabric 只提供了几个基础图形,比如矩形、圆形、三角形等,如果要使用 箭头直线 ,就需要自定义图像实现
创建类
使用下面函数创建类
fabric.util.createClass(父类,{
// 初始化方法,相当于构造方法
initialize: function(points, options){
},
// 渲染图形,使用 canvas 原生方式
_render: function(ctx) {
}
})
参数解释:
- 父类:表示继承该类
- {}:是传入的属性、方法
分析 fabric.Line 源码
这里以 fabric.Line 类为例子,进行分析:
// 第一个参数:fabric.Object,表示继承该类
// 第二个参数:{},是传入的属性、方法
fabric.Line = fabric.util.createClass(fabric.Object, {
/**
* Type of an object
* @type String
* @default
*/
type: 'line',
/**
* x value or first line edge
* @type Number
* @default
*/
x1: 0,
/**
* y value or first line edge
* @type Number
* @default
*/
y1: 0,
/**
* x value or second line edge
* @type Number
* @default
*/
x2: 0,
/**
* y value or second line edge
* @type Number
* @default
*/
y2: 0,
/**
* Constructor 初始化方法,相当于构造方法
* @param {Array} [points] Array of points
* @param {Object} [options] Options object
* @return {fabric.Line} thisArg
*/
initialize: function(points, options) {
if (!points) {
points = [0, 0, 0, 0];
}
this.callSuper('initialize', options); // 调用父类的 initialize 初始化方法
this.set('x1', points[0]);
this.set('y1', points[1]);
this.set('x2', points[2]);
this.set('y2', points[3]);
this._setWidthHeight(options);
},
/**
* @private 计算宽度高度
* @param {Object} [options] Options
*/
_setWidthHeight: function(options) {
options || (options = { });
this.width = Math.abs(this.x2 - this.x1);
this.height = Math.abs(this.y2 - this.y1);
this.left = 'left' in options
? options.left
: this._getLeftToOriginX();
this.top = 'top' in options
? options.top
: this._getTopToOriginY();
},
/**
* @private 渲染图形,使用 canvas 原生方式
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx) {
ctx.beginPath();
// 重新计算开始点、结束点的位置,因为是以中心点为参考点,即:中心点的左侧、上部都是负数
var p = this.calcLinePoints();
ctx.moveTo(p.x1, p.y1);
ctx.lineTo(p.x2, p.y2);
ctx.lineWidth = this.strokeWidth;
// TODO: test this
// make sure setting "fill" changes color of a line
// (by copying fillStyle to strokeStyle, since line is stroked, not filled)
var origStrokeStyle = ctx.strokeStyle;
ctx.strokeStyle = this.stroke || ctx.fillStyle;
this.stroke && this._renderStroke(ctx);
ctx.strokeStyle = origStrokeStyle;
},
})
例子
<canvas id="canvas"></canvas>
定义图形类
<script>
// Initiate a canvas instance
var canvas = new fabric.Canvas("canvas");
canvas.setWidth(document.body.scrollWidth);
canvas.setHeight(250);
const ArrowLine = fabric.util.createClass(fabric.Line,{
type:"arrowLine", // 指定类型
/**
* Constructor
* @param {Array} [points] 开始点x,y坐标
* @param {number} [length] 箭头直线的长度
* @param {Object} [options] Options object
* @return {fabric.Line} thisArg
*/
initialize: function(points,length, options) {
if (!points) {
points = [0, 0];
}
// 计算终点位置
const end = [ points[0] + length , points[1]]
// 添加到 points中,传给 父类构造方法,让其计算
points.push( end[0] )
points.push( end[1] )
this.callSuper('initialize', points,options)
},
/**
* @private 绘制图形,调用 canvas 原生方法
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx) {
ctx.beginPath();
// 计算点的位置,这里的坐标,是以中心点为参考点
var p = this.calcLinePoints();
// 向右箭头的水平宽度
const widthArrow = this._getArrowWidth()
ctx.moveTo(p.x1, p.y1);
ctx.lineTo( this._getLineEndX(p), p.y2);
this._drawRightArrow(ctx,p)
ctx.lineWidth = this.strokeWidth;
// TODO: test this
// make sure setting "fill" changes color of a line
// (by copying fillStyle to strokeStyle, since line is stroked, not filled)
var origStrokeStyle = ctx.strokeStyle;
// ctx.strokeStyle = this.stroke || ctx.fillStyle;
// ctx.fillStyle = this.fill
this.stroke && this._renderStroke(ctx);
// 填充箭头颜色
this.fill && this._renderFill(ctx);
ctx.strokeStyle = origStrokeStyle;
},
/**
* 获取向右箭头水平宽度
*/
_getArrowWidth(){
// 向右箭头的水平宽度
return this.strokeWidth * 2 + 8
},
/**
* 直线结束 x 的坐标
* @param {Object} p
*/
_getLineEndX(p){
const widthArrow = this._getArrowWidth()
return p.x2 - widthArrow // 直线减去箭头的水平宽度
},
/**
* 绘制向右箭头箭头
* @param {Object} ctx
* @param {Object} p
*/
_drawRightArrow(ctx,p){
// 向右箭头的垂直高度
const heightArrow = this.strokeWidth * 2 + 5
// 向右箭头的水平宽度
const widthArrow = this._getArrowWidth()
// console.log("widthArrow:",widthArrow)
// 直线的长度
const lengthLine = this.width - widthArrow
// 直线结束点位置
const end = [ this._getLineEndX(p),0]
// 向右箭头,右点位置
const rightArrow = [end[0] + widthArrow , 0]
// 上点位置
const topArrow = [end[0], -heightArrow/2]
// 下点位置
const bottomArrow = [end[0],heightArrow/2]
ctx.moveTo(rightArrow[0],rightArrow[1])
ctx.lineTo(topArrow[0],topArrow[1])
ctx.lineTo(bottomArrow[0],bottomArrow[1])
ctx.closePath()
},
})
// 只能左右拉伸
function setControlVisible(element){
element['setControlVisible']('mt',false)
element['setControlVisible']('mb',false)
element['setControlVisible']('tl',false)
element['setControlVisible']('bl',false)
element['setControlVisible']('tr',false)
element['setControlVisible']('br',false)
}
const arrowLine = new ArrowLine([10,10] , 200 ,{
left:100,
top:100,
strokeWidth: 3,
stroke: "green",
fill: "green",
})
setControlVisible(arrowLine)
canvas.add(arrowLine)
console.log(arrowLine.width,arrowLine.height)
arrowLine.width = 300 // 动态改变宽度
arrowLine.setCoords()
</script>
参考:
https://cloud.tencent.com/developer/article/2119137
https://www.cnblogs.com/k21vin/articles/17791973.html