最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Arrows in fabricjs - Stack Overflow

programmeradmin0浏览0评论

I'm trying to create an arrow shape using fabricjs. Thus far my best approach has been to add a line and a triangle and bine them into a posite group. The problem however is, when I resize the arrow, the arrow head gets stretched and its not a nice effect.

What I'm asking is, how would you go about creating an arrow object on fabricjs that can be resized lengthwise only without stretching the arrow head. /

<html>
	<head>
		<script src=',gestures,easing,parser,freedrawing,interaction,serialization,image_filters,gradient,pattern,shadow,node.js'></script>		<meta charset="utf-8">

		<style>
			html,body
			{
				height: 100%; min-height:100%;
				width: 100%; min-width:100%;
				background-color:transparent;
				margin:0;
			}
			button
			{
				height:44px;
				margin:0;
			}
		</style>

	</head>
	<body>

		<span id="dev">
			<button id="draw_mode" onclick="toggleDraw()">Draw</button>
			<button onclick="addRect()">Add Rect</button>
			<button onclick="addCircle()">Add Circle</button>
			<button onclick="addTriangle()">Add Triangle</button>
			<button onclick="addLine()">Add Line</button>
			<button onclick="addArrow()">Add Arrow</button>
			<button onclick="clearCanvas()">Clear</button>
			<button onclick="saveCanvas()">Save</button>
			<button onclick="loadCanvas()">Load</button>
		</span>
		<span id="selected" style="visibility:hidden;">
			<button onclick="removeSelected()">Remove</button>
		</span>
		<canvas id="c" style="border:1px solid #aaa;"></canvas>

		<script>

		fabric.Object.prototype.toObject = (function (toObject)
		{
    	return function ()
			{
        return fabric.util.object.extend(toObject.call(this),
				{
            id:this.id,
        });
    	};
		})(fabric.Object.prototype.toObject);

		fabric.LineArrow = fabric.util.createClass(fabric.Line, {

  type: 'lineArrow',

  initialize: function(element, options) {
    options || (options = {});
    this.callSuper('initialize', element, options);
  },

  toObject: function() {
    return fabric.util.object.extend(this.callSuper('toObject'));
  },

  _render: function(ctx){
    this.callSuper('_render', ctx);

    // do not render if width/height are zeros or object is not visible
    if (this.width === 0 || this.height === 0 || !this.visible) return;

    ctx.save();

    var xDiff = this.x2 - this.x1;
    var yDiff = this.y2 - this.y1;
    var angle = Math.atan2(yDiff, xDiff);
    ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2);
    ctx.rotate(angle);
    ctx.beginPath();
    //move 10px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
    ctx.moveTo(10,0);
    ctx.lineTo(-20, 15);
    ctx.lineTo(-20, -15);
    ctx.closePath();
    ctx.fillStyle = this.stroke;
    ctx.fill();

    ctx.restore();

  }
});

fabric.LineArrow.fromObject = function (object, callback) {
    callback && callback(new fabric.LineArrow([object.x1, object.y1, object.x2, object.y2],object));
};

fabric.LineArrow.async = true;

		var canvas = new fabric.Canvas('c');
		canvas.isDrawingMode = false;
		canvas.freeDrawingBrush.width = 5;
		setColor('red');

		var sendToApp = function(_key, _val)
		{
 			var iframe = document.createElement("IFRAME");
 			iframe.setAttribute("src", _key + ":##drawings##" + _val);
 			document.documentElement.appendChild(iframe);
 			iframe.parentNode.removeChild(iframe);
 			iframe = null;
		};

		canvas.on('object:selected',function(options)
		{
  		if (options.target)
			{
    		//console.log('an object was selected! ', options.target.type);
				var sel = document.getElementById("selected");
				sel.style.visibility = "visible";
				sendToApp("object:selected","");
  		}
		});

		canvas.on('selection:cleared',function(options)
		{
			//console.log('selection cleared');
			var sel = document.getElementById("selected");
			sel.style.visibility = "hidden";
			sendToApp("selection:cleared","");
		});

		canvas.on('object:modified',function(options)
		{
  		if (options.target)
			{
    		//console.log('an object was modified! ', options.target.type);
				sendToApp("object:modified","");
  		}
		});

		canvas.on('object:added',function(options)
		{
  		if (options.target)
			{
				if (typeof options.target.id == 'undefined')
				{
					options.target.id = 1337;
				}
    		//console.log('an object was added! ', options.target.type);
				sendToApp("object:added","");
  		}
		});

		canvas.on('object:removed',function(options)
		{
			if (options.target)
			{
				//console.log('an object was removed! ', options.target.type);
				sendToApp("object:removed","");
			}
		});

		window.addEventListener('resize', resizeCanvas, false);

		function resizeCanvas()
		{
			canvas.setHeight(window.innerHeight);
			canvas.setWidth(window.innerWidth);
			canvas.renderAll();
		}

		function color()
		{
			return canvas.freeDrawingBrush.color;
		}

		function setColor(color)
		{
			canvas.freeDrawingBrush.color = color;
		}

		function toggleDraw()
		{
			setDrawingMode(!canvas.isDrawingMode);
		}

		function setDrawingMode(isDrawingMode)
		{
			canvas.isDrawingMode = isDrawingMode;
			var btn = document.getElementById("draw_mode");
			btn.innerHTML = canvas.isDrawingMode ? "Drawing" : "Draw";
			sendToApp("mode",canvas.isDrawingMode ? "drawing" : "draw");
		}

		function setLineControls(line)
		{
			line.setControlVisible("tr",false);
			line.setControlVisible("tl",false);
			line.setControlVisible("br",false);
			line.setControlVisible("bl",false);
			line.setControlVisible("ml",false);
			line.setControlVisible("mr",false);
		}

		function createLine(points)
		{
			var line = new fabric.Line(points,
			{
				strokeWidth: 5,
				stroke: color(),
				originX: 'center',
				originY: 'center',
				lockScalingX:true,
				//lockScalingY:false,
			});
			setLineControls(line);
			return line;
		}

		function createArrowHead(points)
		{
			var headLength = 15,

					x1 = points[0],
					y1 = points[1],
					x2 = points[2],
					y2 = points[3],

					dx = x2 - x1,
					dy = y2 - y1,

					angle = Math.atan2(dy, dx);

			angle *= 180 / Math.PI;
			angle += 90;

			var triangle = new fabric.Triangle({
				angle: angle,
				fill: color(),
				top: y2,
				left: x2,
				height: headLength,
				width: headLength,
				originX: 'center',
				originY: 'center',
				// lockScalingX:false,
				// lockScalingY:true,
			});

			return triangle;
		}

		function addRect()
		{
			canvas.add(new fabric.Rect({left:100,top:100,fill:color(),width:50,height:50}));
		}

		function addCircle()
		{
			canvas.add(new fabric.Circle({left:150,top:150,fill:color(),radius:50/2}));
		}

		function addTriangle()
		{
			canvas.add(new fabric.Triangle({left:200,top:200,fill:color(),height:50,width:46}));
		}

		function addLine()
		{
			var line = createLine([100,100,100,200]);
			canvas.add(line);
		}

		function addArrow()
		{
			var pts = [100,100,100,200];
			var triangle = createArrowHead(pts);
			var line = createLine(pts);
			var grp = new fabric.Group([triangle,line]);			
			setLineControls(grp);
			canvas.add(grp);
			// var arrow = new fabric.LineArrow(pts,{left:100,top:100,fill:color()});
			// setLineControls(arrow);
			// canvas.add(arrow);
		}

		function removeSelected()
		{
			var grp = canvas.getActiveGroup();
			var obj = canvas.getActiveObject();
			if (obj!=null)
			{
				canvas.remove(obj);
			}
			if (grp!=null)
			{
				grp.forEachObject(function(o){ canvas.remove(o) });
				canvas.discardActiveGroup().renderAll();
			}
		}

		function clearCanvas()
		{
			canvas.clear();
		}

		function saveCanvas()
		{
			var js = JSON.stringify(canvas);
			return js;
		}

		function loadCanvas()
		{
			var js = '{"objects":[{"type":"circle","originX":"left","originY":"top","left":150,"top":150,"width":50,"height":50,"fill":"red","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","id":1234,"radius":25,"startAngle":0,"endAngle":6.283185307179586}],"background":""}';
			canvas.loadFromJSON(js);
		}

		resizeCanvas();

		</script>
	</body>
</html>

I'm trying to create an arrow shape using fabricjs. Thus far my best approach has been to add a line and a triangle and bine them into a posite group. The problem however is, when I resize the arrow, the arrow head gets stretched and its not a nice effect.

What I'm asking is, how would you go about creating an arrow object on fabricjs that can be resized lengthwise only without stretching the arrow head. http://jsfiddle/skela/j45czqge/

<html>
	<head>
		<script src='http://fabricjs./build/files/text,gestures,easing,parser,freedrawing,interaction,serialization,image_filters,gradient,pattern,shadow,node.js'></script>		<meta charset="utf-8">

		<style>
			html,body
			{
				height: 100%; min-height:100%;
				width: 100%; min-width:100%;
				background-color:transparent;
				margin:0;
			}
			button
			{
				height:44px;
				margin:0;
			}
		</style>

	</head>
	<body>

		<span id="dev">
			<button id="draw_mode" onclick="toggleDraw()">Draw</button>
			<button onclick="addRect()">Add Rect</button>
			<button onclick="addCircle()">Add Circle</button>
			<button onclick="addTriangle()">Add Triangle</button>
			<button onclick="addLine()">Add Line</button>
			<button onclick="addArrow()">Add Arrow</button>
			<button onclick="clearCanvas()">Clear</button>
			<button onclick="saveCanvas()">Save</button>
			<button onclick="loadCanvas()">Load</button>
		</span>
		<span id="selected" style="visibility:hidden;">
			<button onclick="removeSelected()">Remove</button>
		</span>
		<canvas id="c" style="border:1px solid #aaa;"></canvas>

		<script>

		fabric.Object.prototype.toObject = (function (toObject)
		{
    	return function ()
			{
        return fabric.util.object.extend(toObject.call(this),
				{
            id:this.id,
        });
    	};
		})(fabric.Object.prototype.toObject);

		fabric.LineArrow = fabric.util.createClass(fabric.Line, {

  type: 'lineArrow',

  initialize: function(element, options) {
    options || (options = {});
    this.callSuper('initialize', element, options);
  },

  toObject: function() {
    return fabric.util.object.extend(this.callSuper('toObject'));
  },

  _render: function(ctx){
    this.callSuper('_render', ctx);

    // do not render if width/height are zeros or object is not visible
    if (this.width === 0 || this.height === 0 || !this.visible) return;

    ctx.save();

    var xDiff = this.x2 - this.x1;
    var yDiff = this.y2 - this.y1;
    var angle = Math.atan2(yDiff, xDiff);
    ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2);
    ctx.rotate(angle);
    ctx.beginPath();
    //move 10px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
    ctx.moveTo(10,0);
    ctx.lineTo(-20, 15);
    ctx.lineTo(-20, -15);
    ctx.closePath();
    ctx.fillStyle = this.stroke;
    ctx.fill();

    ctx.restore();

  }
});

fabric.LineArrow.fromObject = function (object, callback) {
    callback && callback(new fabric.LineArrow([object.x1, object.y1, object.x2, object.y2],object));
};

fabric.LineArrow.async = true;

		var canvas = new fabric.Canvas('c');
		canvas.isDrawingMode = false;
		canvas.freeDrawingBrush.width = 5;
		setColor('red');

		var sendToApp = function(_key, _val)
		{
 			var iframe = document.createElement("IFRAME");
 			iframe.setAttribute("src", _key + ":##drawings##" + _val);
 			document.documentElement.appendChild(iframe);
 			iframe.parentNode.removeChild(iframe);
 			iframe = null;
		};

		canvas.on('object:selected',function(options)
		{
  		if (options.target)
			{
    		//console.log('an object was selected! ', options.target.type);
				var sel = document.getElementById("selected");
				sel.style.visibility = "visible";
				sendToApp("object:selected","");
  		}
		});

		canvas.on('selection:cleared',function(options)
		{
			//console.log('selection cleared');
			var sel = document.getElementById("selected");
			sel.style.visibility = "hidden";
			sendToApp("selection:cleared","");
		});

		canvas.on('object:modified',function(options)
		{
  		if (options.target)
			{
    		//console.log('an object was modified! ', options.target.type);
				sendToApp("object:modified","");
  		}
		});

		canvas.on('object:added',function(options)
		{
  		if (options.target)
			{
				if (typeof options.target.id == 'undefined')
				{
					options.target.id = 1337;
				}
    		//console.log('an object was added! ', options.target.type);
				sendToApp("object:added","");
  		}
		});

		canvas.on('object:removed',function(options)
		{
			if (options.target)
			{
				//console.log('an object was removed! ', options.target.type);
				sendToApp("object:removed","");
			}
		});

		window.addEventListener('resize', resizeCanvas, false);

		function resizeCanvas()
		{
			canvas.setHeight(window.innerHeight);
			canvas.setWidth(window.innerWidth);
			canvas.renderAll();
		}

		function color()
		{
			return canvas.freeDrawingBrush.color;
		}

		function setColor(color)
		{
			canvas.freeDrawingBrush.color = color;
		}

		function toggleDraw()
		{
			setDrawingMode(!canvas.isDrawingMode);
		}

		function setDrawingMode(isDrawingMode)
		{
			canvas.isDrawingMode = isDrawingMode;
			var btn = document.getElementById("draw_mode");
			btn.innerHTML = canvas.isDrawingMode ? "Drawing" : "Draw";
			sendToApp("mode",canvas.isDrawingMode ? "drawing" : "draw");
		}

		function setLineControls(line)
		{
			line.setControlVisible("tr",false);
			line.setControlVisible("tl",false);
			line.setControlVisible("br",false);
			line.setControlVisible("bl",false);
			line.setControlVisible("ml",false);
			line.setControlVisible("mr",false);
		}

		function createLine(points)
		{
			var line = new fabric.Line(points,
			{
				strokeWidth: 5,
				stroke: color(),
				originX: 'center',
				originY: 'center',
				lockScalingX:true,
				//lockScalingY:false,
			});
			setLineControls(line);
			return line;
		}

		function createArrowHead(points)
		{
			var headLength = 15,

					x1 = points[0],
					y1 = points[1],
					x2 = points[2],
					y2 = points[3],

					dx = x2 - x1,
					dy = y2 - y1,

					angle = Math.atan2(dy, dx);

			angle *= 180 / Math.PI;
			angle += 90;

			var triangle = new fabric.Triangle({
				angle: angle,
				fill: color(),
				top: y2,
				left: x2,
				height: headLength,
				width: headLength,
				originX: 'center',
				originY: 'center',
				// lockScalingX:false,
				// lockScalingY:true,
			});

			return triangle;
		}

		function addRect()
		{
			canvas.add(new fabric.Rect({left:100,top:100,fill:color(),width:50,height:50}));
		}

		function addCircle()
		{
			canvas.add(new fabric.Circle({left:150,top:150,fill:color(),radius:50/2}));
		}

		function addTriangle()
		{
			canvas.add(new fabric.Triangle({left:200,top:200,fill:color(),height:50,width:46}));
		}

		function addLine()
		{
			var line = createLine([100,100,100,200]);
			canvas.add(line);
		}

		function addArrow()
		{
			var pts = [100,100,100,200];
			var triangle = createArrowHead(pts);
			var line = createLine(pts);
			var grp = new fabric.Group([triangle,line]);			
			setLineControls(grp);
			canvas.add(grp);
			// var arrow = new fabric.LineArrow(pts,{left:100,top:100,fill:color()});
			// setLineControls(arrow);
			// canvas.add(arrow);
		}

		function removeSelected()
		{
			var grp = canvas.getActiveGroup();
			var obj = canvas.getActiveObject();
			if (obj!=null)
			{
				canvas.remove(obj);
			}
			if (grp!=null)
			{
				grp.forEachObject(function(o){ canvas.remove(o) });
				canvas.discardActiveGroup().renderAll();
			}
		}

		function clearCanvas()
		{
			canvas.clear();
		}

		function saveCanvas()
		{
			var js = JSON.stringify(canvas);
			return js;
		}

		function loadCanvas()
		{
			var js = '{"objects":[{"type":"circle","originX":"left","originY":"top","left":150,"top":150,"width":50,"height":50,"fill":"red","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","id":1234,"radius":25,"startAngle":0,"endAngle":6.283185307179586}],"background":""}';
			canvas.loadFromJSON(js);
		}

		resizeCanvas();

		</script>
	</body>
</html>

Share Improve this question edited Aug 18, 2018 at 1:30 Machavity 31.7k27 gold badges95 silver badges105 bronze badges asked Jul 6, 2015 at 4:55 SkelaSkela 90410 silver badges18 bronze badges 1
  • This answer may help you: stackoverflow./a/20688100/1479630 . – kolenda Commented Jul 15, 2015 at 15:33
Add a ment  | 

2 Answers 2

Reset to default 5

I had the same problem and ended up doing math to calculate the points that would make up an arrow shape around a line and using a polygon object instead.

The core of it looks like:

var angle = Math.atan2(toy - fromy, tox - fromx);

var headlen = 15;  // arrow head size

// bring the line end back some to account for arrow head.
tox = tox - (headlen) * Math.cos(angle);
toy = toy - (headlen) * Math.sin(angle);

// calculate the points.
var points = [
    {
        x: fromx,  // start point
        y: fromy
    }, {
        x: fromx - (headlen / 4) * Math.cos(angle - Math.PI / 2), 
        y: fromy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
    },{
        x: tox - (headlen / 4) * Math.cos(angle - Math.PI / 2), 
        y: toy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
    }, {
        x: tox - (headlen) * Math.cos(angle - Math.PI / 2),
        y: toy - (headlen) * Math.sin(angle - Math.PI / 2)
    },{
        x: tox + (headlen) * Math.cos(angle),  // tip
        y: toy + (headlen) * Math.sin(angle)
    }, {
        x: tox - (headlen) * Math.cos(angle + Math.PI / 2),
        y: toy - (headlen) * Math.sin(angle + Math.PI / 2)
    }, {
        x: tox - (headlen / 4) * Math.cos(angle + Math.PI / 2),
        y: toy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
    }, {
        x: fromx - (headlen / 4) * Math.cos(angle + Math.PI / 2),
        y: fromy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
    },{
        x: fromx,
        y: fromy
    }
];

Then create a polygon from the points.

https://jsfiddle/6e17oxc3/

What you can do is calculate the new size after the object is stretched and draw another object on the same exact area and removing the previous one.

var obj = canvas.getActiveObject();
var width = obj.getWidth();
var height = obj.getHeight;
var top = obj.getTop();

Now if you have only one object that is stretched, you can simply use the data above to draw another nicely looking object on the canvas. If you have multiples then you need to get the data for all of them and draw them one by one.

发布评论

评论列表(0)

  1. 暂无评论