FabricJS: bug with opacity of circles in a group? - javascript

When I add a circle with an opacity lower than 1 to a group, its opacity becomes actually lower than the specified value. This does not happen if I don't specify opacity (i.e., opacity = 1). It also doesn't happen with a rectangle.
Here is the code to reproduce this issue:
HTML
<canvas id="stage" width="400" height="300">
JavaScript
var OPACITY = 0.65;
var FILL = '#fff';
var canvas = new fabric.Canvas('stage', {
backgroundColor: '#222'
});
/**
* Rectangles
* both appear to have the same color
*/
var rect1 = new fabric.Rect({
width: 40,
height: 40,
fill: FILL,
opacity: OPACITY,
left: 60,
top: 60
});
canvas.add(rect1);
var rect2 = new fabric.Rect({
width: 40,
height: 40,
fill: FILL
opacity: OPACITY,
});
var rect2Group = new fabric.Group([rect2], {
left: 120,
top: 60
});
canvas.add(rect2Group);
/**
* Circles
* the second circle is darker
*/
var circle1 = new fabric.Circle({
radius: 20,
fill: FILL,
opacity: OPACITY,
left: 60,
top: 120
});
canvas.add(circle1);
var circle2 = new fabric.Circle({
radius: 20,
fill: FILL,
opacity: OPACITY,
});
var circle2Group = new fabric.Group([circle2], {
left: 120,
top: 120
});
canvas.add(circle2Group);
Here is the JSFiddle.
If you run it, you can see that the second circle is darker than the first one, meaning that its opacity is lower.
Am I doing something wrong, or is this a bug? (Could be reproduced in 1.2.0 and 1.3.0.)

It's most likely because of this line in fabric.Circle:
// multiply by currently set alpha
// (the one that was set by path group where this object is contained, for example)
ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity;
This has to do with circles being part of SVG group, IIRC.
In any case, it's definitely a bug — the opacity shouldn't be multiplied in your case. We need to have a better check.
Please file an issue on github.

Related

outline and border in fabricjs

I am using fabric.js for canvas shapes. but now i have to add border with outline on these shapes like below. How it is possible in fabricjs??. Or do we have any other js library to get same output?
I want below output:
Why not create a group out of two rectangles? Like so:
var canvas = this.__canvas = new fabric.StaticCanvas('c');
var rectBack = new fabric.Rect({
width: 170,
height: 170,
top: 0,
left: 0,
fill: 'rgba(0,0,255,1.0)',
rx: 2,
ry: 2
});
var outerMargin = 10
var innerOutlineWidth = 4
var innerOutline = new fabric.Rect({
width: 170 - outerMargin - innerOutlineWidth/2,
height: 170 - outerMargin - innerOutlineWidth/2,
top: outerMargin/2,
left: outerMargin/2,
stroke: 'rgba(255,255,255,1.0)',
fill: 'rgba(0,0,0,0.0)',
strokeWidth: innerOutlineWidth,
rx: 10,
ry: 10
});
var group = new fabric.Group([rectBack, innerOutline], {
left: 0,
top: 0,
angle: 0
});
canvas.add(group);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.min.js"></script>
<canvas id="c" width="200" height="200"></canvas>
Any framework you use is going to have some fundamental building blocks you have to piece together to get what you want. So I would not recommend jumping to another one.

Animate drawing of path on HTML5 canvas

My problem is how to animate the drawing of a path between two points.
Consider a curved line or path between two points, A & B. I can draw this easily on the canvas using the line drawing functions in Konvajs.
However, what I actually want is to animate the revealing of the line so that it starts from point A and progressively draws to point B. The reveal should be animated so I can apply pleasing easings.
As a comparable example, see the brief video on this site https://coggle.it/ where the video shows the creation of a new box and the line draws to connect it to the old.
Here is a potential answer (special thanks to #markov00 re same technique in SVG). It works by manipulating the path dashOffset and dash attributes. There is an excellent explanation of the technique here in a post by Jake Archibald which also includes an interactive experiment with sliders which I found very useful.
I have tried to make the demo as lightweight as possible and just show the technique - though I added a slider and some UI to help understand the process. The use of jquery is only for the UI parts which are not needed for the technique.
Couple of points:
The demo here uses a path from 3 straight line segments. But I tried curves and combination paths and the technique works in those cases too - so any path should work.
I found that using a close-path command (z) on the path caused the path length function to be short on the true distance. This appears as an amount of the path remaining stroked or gapped at either end, with the size depending on the jump between first & last to close the path.
The path length is virtually always going to be decimal so don't try to do everything as integers as you will ultimately find your stroke is slightly overlong or short.
To adopt this for animation and easing etc, take the couple of lines from the slider change event and stick them inside the frame callback, manipulating the maths to suit your case.
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 320, height: 180});
// Add a layer
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
// show where the start of the path is.
var circle = new Konva.Circle({
x: 66,
y: 15,
radius: 5,
stroke: 'red'
})
layer.add(circle);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
data: 'M66 15 L75 100 L225 120 L100 17 L66 15',
stroke: 'green'
});
// get the path length and set this as the dash and dashOffset.
var pathLen = path.getLength();
path.dashOffset(pathLen);
path.dash([pathLen]);
layer.add(path)
stage.draw();
// Some UI bits
$('#dist').attr('max', parseInt(pathLen)); // set slider max to length of path
$('#pathLen').html('Path : ' + pathLen); // display path length
// jquery event listener on slider change
$('#dist').on('input', function(){
// compute the new dash lenth as original path length - current slider value.
// Means that dashLen initially = path len and moves toward zero as slider val increases.
var dashLen = pathLen - $(this).val();;
path.dashOffset(dashLen); // set new value
layer.draw(); // refresh the layer to see effect
// update the UI elements
$('#dashLen').html('Dash: ' + dashLen);
$('#pathPC').html(parseInt(100-(100 * (dashLen/pathLen)), 10) + '%');
})
.info
{
padding-left: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div class="slidecontainer">
<input class='slider' id='dist' type="range" min="0" max="100" value="0" class="slider" id="myRange"/>
<span class='info' id='pathPC'></span>
<span class='info' id='pathLen'></span>
<span class='info' id='dashLen'></span>
</div>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
My solution with animation:
var width = window.innerWidth;
var height = window.innerHeight;
// Set up the canvas / stage
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
// Add a layer
var layer = new Konva.Layer({
draggable: false
});
stage.add(layer);
// show where the start of the path is.
var circle = new Konva.Circle({
x: 66,
y: 15,
radius: 5,
stroke: 'red'
})
layer.add(circle);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
data: 'M66 15 L75 100 L225 120 L100 17 L66 15',
stroke: 'green'
});
// get the path length and set this as the dash and dashOffset.
var pathLen = path.getLength();
path.dashOffset(pathLen);
path.dash([pathLen]);
// make some animation with stop
var anim = new Konva.Animation(function (frame) {
var dashLen = pathLen - frame.time / 5;
path.dashOffset(dashLen);
if (dashLen < 0) {
anim.stop();
}
}, layer);
anim.start();
layer.add(path)
stage.draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div id='container' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
And here is an alternative to #Roxane's animated version but using a tween.
var width = window.innerWidth;
var height = window.innerHeight;
// Set up the canvas / stage
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
// Add a layer
var layer = new Konva.Layer({
draggable: false
});
stage.add(layer);
// show where the start of the path is.
var circle = new Konva.Circle({
x: 66,
y: 15,
radius: 5,
stroke: 'red'
})
layer.add(circle);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
data: 'M66 15 L75 100 L225 120 L100 17 L66 15',
stroke: 'green'
});
// get the path length and set this as the dash and dashOffset.
var pathLen = path.getLength();
path.dashOffset(pathLen);
path.dash([pathLen]);
layer.add(path); // have to add to layer for tweening.
// create the tween
var tween = new Konva.Tween({
node: path,
dashOffset: 0,
easing: Konva.Easings['BounceEaseOut'],
duration: 1.5
});
tween.play(); // execute the tween
stage.draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div id='container' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>

Unable to maintain thickness of strokeWidth while resizing in case of Groups in Fabricjs

I am writing code to maintain same strokeWidth across all the objects in Fabricjs.
I am able to successfully maintain strokeWidth for normal Objects , let it be rectangle or ellipse(I wrote code to maintain both).
But when I use group , I am unable to maintain the strokeWidth.
Here's the fiddle.
Steps to reproduce the issue ?
1)Open above fiddle link
2)resize the circle , check the size of border (thickness of border remains the same on re-sizing)
3)Now resize the rectangle and text(group) , expected result is thickness of border must be same throughout but that doesn't happens.
Code :
`
var gCanvas = new fabric.Canvas('canvDraw');
gCanvas.setWidth(700);
gCanvas.setHeight(700);
var text = new fabric.Text('test', { fontSize: 30, top: 100, left : 100, fill : 'red'});
// add some shapes - these are examples, others should work too
var myRectangle = new fabric.Rect({
left: 100,
top: 100,
fill: '#999999',
width: 120,
height: 120,
strokeWidth: 3,
fill: '',
stroke: '#000000'
});
var group = new fabric.Group([ myRectangle, text ], {borderColor: 'black', cornerColor: 'green', lockScalingFlip : true});
gCanvas.add(group);
var myEllipse = new fabric.Ellipse({
top: 250,
left: 100,
rx: 75,
ry: 50,
fill: '',
stroke: '#000000',
strokeWidth: 3
});
gCanvas.add(myEllipse);
gCanvas.observe('object:scaling', function (e) {
e.target.resizeToScale();
});
fabric.Object.prototype.resizeToScale = function () {
// resizes an object that has been scaled (e.g. by manipulating the handles), setting scale to 1 and recalculating bounding box where necessary
switch (this.type) {
case "ellipse":
this.rx *= this.scaleX;
this.ry *= this.scaleY;
this.width = this.rx * 2;
this.height = this.ry * 2;
this.scaleX = 1;
this.scaleY = 1;
break;
case "rect":
this.width *= this.scaleX;
this.height *= this.scaleY;
this.scaleX = 1;
this.scaleY = 1;
default:
break;
}
}
`
this approach will not work for all kind of shapes. when facing text or polygons you cannot recalculate everything.
you can do something like that:
fabric.Object.prototype.resizeToScale = function () {
if (this.type !=='group') {
this.strokeWidth = this._origStrokeWidth / Math.max(this.scaleX, this.scaleY);
}
else {
this._objects.forEach( function(obj){
console.log(obj);
obj.strokeWidth = obj._origStrokeWidth / Math.max(obj.group.scaleX, obj.group.scaleY);
});
}
}
if you prefer the effect of your solution (border is always uniform) put this line of code in the default case of your switch construct to handle all the types for wich you cannot find a resizing solution.
edit i added group handling.
The point is to make smaller strokewidth when the group / object scale.
Because when you will have polygons and polylines and paths, changing width / height /radius will not help.
http://jsfiddle.net/y344vs5s/4/

when two fabricjs rects beside each other

Why do i have a horizontal gray line in the middle of two rects ? Even when i set hasBorder to false!
fabric.Rect.prototype.backgroundColor = '#000'
fabric.Rect.prototype.opacity = 0.7
fabric.Rect.prototype.selectable = false
fabric.Rect.prototype.hasBorder = false
var canvas = new fabric.Canvas('c')
var rect1 = new fabric.Rect({
left: 0,
top: 0,
width: 500,
height: 100
})
var rect2 = new fabric.Rect({
left: 0,
top: 100,
width: 500,
height: 100
})
canvas.add(rect1, rect2)
see fiddle here
Any answer will be appreciated, thanks!
It is hasBorders (instead of hasBorder)
Fiddle - http://jsfiddle.net/m7djt7ty/
I finally find the answer to the gray line appearing is because the rect's height is decimals, maybe my math calc should be more precise! Or to use another way to calc with decimals.
Thanks again!

How to put canvas within canvas?

I just want to know what is the best way to use multiple canvas in a single page. These canvas can be overlapped on each other.
I tried to search this issue on different form, but wasn't able to find any helpful material. This is what we actually want to do(in the following image). There are 5 canvases, and we want all of them to be fully functional. We can add images, text and draw different things on selected canvas.
We are currently using fabricjs.
If that`s not possible, what is the best solution for achieving something like that ?
Thanks in advance!
Simply use CSS for that.
<div class="wrapper">
<canvas id="background_layer" class="canvas-layer" width="100" height="100"></canvas>
<canvas id="other_layer" class="canvas-layer" width="100" height="100"></canvas>
</div>
<style>
.wrapper { position: relative }
.canvas-layer {
position: absolute; left: 0; top: 0;
}
</style>
I am not sure what you are trying to achieve but you can refer to this Fiddle http://jsfiddle.net/PromInc/ZxYCP/
var img01URL = 'https://www.google.com/images/srpr/logo4w.png';
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var canvas = new fabric.Canvas('c');
// Note the use of the `originX` and `originY` properties, which we set
// to 'left' and 'top', respectively. This makes the math in the `clipTo`
// functions a little bit more straight-forward.
var clipRect1 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 180,
top: 10,
width: 200,
height: 200,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect1.set({
clipFor: 'pug'
});
canvas.add(clipRect1);
var clipRect2 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 10,
top: 10,
width: 150,
height: 150,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect2.set({
clipFor: 'logo'
});
canvas.add(clipRect2);
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
// Since the `angle` property of the Image object is stored
// in degrees, we'll use this to convert it to radians.
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth;
var ctxHeight = clipRect.height - clipRect.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
var pugImg = new Image();
pugImg.onload = function (img) {
var pug = new fabric.Image(pugImg, {
angle: 45,
width: 500,
height: 500,
left: 230,
top: 50,
scaleX: 0.3,
scaleY: 0.3,
clipName: 'pug',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
pugImg.src = img02URL;
var logoImg = new Image();
logoImg.onload = function (img) {
var logo = new fabric.Image(logoImg, {
angle: 0,
width: 550,
height: 190,
left: 50,
top: 50,
scaleX: 0.25,
scaleY: 0.25,
clipName: 'logo',
clipTo: function(ctx) {
return _.bind(clipByName, logo)(ctx)
}
});
canvas.add(logo);
};
logoImg.src = img01URL;
I hope this might help.

Categories