I\'m using FabricJS to create a canvas for drawing specific lines and shapes. One of the lines is a wavy line with an arrow similar to this:
I\'ve successfu
As we are drawing from both corners for a line, you can draw wavy line in _render
method of custom class. From the end I draw a line to mid , to show its connected with arrow.
DEMO
var line, isDown, evented;
var canvas = new fabric.Canvas('canvas', {
perPixelTargetFind: true
});
draw();
function selection() {
changeObjSelection(true);
canvas.off('mouse:down');
canvas.off('mouse:move');
canvas.off('mouse:up');
evented = false;
}
function draw() {
changeObjSelection(false);
if (!evented) {
canvas.on('mouse:down', onMouseDown);
canvas.on('mouse:move', onMouseMove);
canvas.on('mouse:up', onMouseUp);
evented = true;
}
}
function clearCanvas() {
canvas.clear();
}
function changeObjSelection(value) {
canvas.selection = value;
canvas.forEachObject(function(obj) {
obj.selectable = value;
})
canvas.requestRenderAll();
}
function onMouseDown(options) {
isDown = true;
var pointer = canvas.getPointer(options.e);
var points = [pointer.x, pointer.y, pointer.x, pointer.y];
line = selectLine(points);
canvas.add(line);
}
function onMouseMove(options) {
if (!isDown) return;
var pointer = canvas.getPointer(options.e);
line.set({
x2: pointer.x,
y2: pointer.y
});
canvas.renderAll();
}
function onMouseUp(options) {
isDown = false;
line.setCoords();
canvas.requestRenderAll();
}
function drawLineWithArrow(points, color) {
return new fabric.LineWithArrow(points, {
strokeWidth: 2,
stroke: color,
objectCaching: false,
selectable: false
})
}
function selectLine(points) {
return drawLineWithArrow(points, 'black');
}
//Wavy line
(function(global) {
'use strict';
if (fabric.LineWithArrow) {
fabric.warn('fabric.LineWithArrow is already defined.');
return;
}
var clone = fabric.util.object.clone;
fabric.LineWithArrow = fabric.util.createClass(fabric.Line, {
type: 'lineWithArrow',
initialize: function(element, options) {
options || (options = {});
this.callSuper('initialize', element, options);
// Set default options
this.set({
hasBorders: false,
hasControls: false,
});
},
_render: function(ctx) {
// this.callSuper('_render', ctx);
ctx.save();
const xDiff = this.x2 - this.x1;
const yDiff = this.y2 - this.y1;
const angle = Math.atan2(yDiff, xDiff);
ctx.translate(xDiff / 2, yDiff / 2);
ctx.rotate(angle);
ctx.beginPath();
// Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
ctx.moveTo(5, 0);
ctx.lineTo(-5, 5);
ctx.lineTo(-5, -5);
ctx.closePath();
ctx.fillStyle = this.stroke;
ctx.fill();
ctx.restore();
var p = this.calcLinePoints();
var point = this.pointOnLine(this.point(p.x2, p.y2), this.point(p.x1, p.y1), 10)
this.wavy(this.point(p.x1, p.y1), point, this.point(p.x2, p.y2), ctx);
ctx.stroke();
},
point: function(x, y) {
return {
x: x,
y: y
};
},
wavy: function(from, to, endPoint, ctx) {
var cx = 0,
cy = 0,
fx = from.x,
fy = from.y,
tx = to.x,
ty = to.y,
i = 0,
step = 4,
waveOffsetLength = 0,
ang = Math.atan2(ty - fy, tx - fx),
distance = Math.sqrt((fx - tx) * (fx - tx) + (fy - ty) * (fy - ty)),
amplitude = -10,
f = Math.PI * distance / 30;
for (i; i <= distance; i += step) {
waveOffsetLength = Math.sin((i / distance) * f) * amplitude;
cx = from.x + Math.cos(ang) * i + Math.cos(ang - Math.PI / 2) * waveOffsetLength;
cy = from.y + Math.sin(ang) * i + Math.sin(ang - Math.PI / 2) * waveOffsetLength;
i > 0 ? ctx.lineTo(cx, cy) : ctx.moveTo(cx, cy);
}
ctx.lineTo(to.x, to.y);
ctx.lineTo(endPoint.x, endPoint.y);
},
pointOnLine: function(point1, point2, dist) {
var len = Math.sqrt(((point2.x - point1.x) * (point2.x - point1.x)) + ((point2.y - point1.y) * (point2.y - point1.y)));
var t = (dist) / len;
var x3 = ((1 - t) * point1.x) + (t * point2.x),
y3 = ((1 - t) * point1.y) + (t * point2.y);
return new fabric.Point(x3, y3);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
customProps: this.customProps,
});
},
});
fabric.LineWithArrow.fromObject = function(object, callback) {
function _callback(instance) {
delete instance.points;
callback && callback(instance);
};
var options = clone(object, true);
options.points = [object.x1, object.y1, object.x2, object.y2];
fabric.Object._fromObject('LineWithArrow', options, _callback, 'points');
};
})(typeof exports !== 'undefined' ? exports : this);
canvas {
border: 2px dotted black;
}