How to keep border width even when object is expanded - fabricjs

My objective in this jsfiddle is to have a Fabric object (for example, a rect) that will "visually" keep its original border width even when it is scaled/expanded. For example, let's say that strokeWidth = 3px; usually when a rect is expanded the user will see the border wider. In my case I need the border to keep its width in screen pixels.
To achieve that, I calculate a factor factorHeight = origHeight / obj.getHeight() every time the object is scaled, and multiply strokeWidth by this factor. This works well when the object is scaled proportionally (try it in the jsfiddle).
However, when the object is not scaled proportionally, the problem is that the top/bottom border widths are different from the right/left border widths. Since there's a single strokeWidth for all these borders I cannot change this behavior. Any ideas?
Javascript:
var canvas = new fabric.Canvas('c' );
var origWidth = 40;
var origHeight = 100;
var origStrokeWidth = 3;
var rect = new fabric.Rect({
top: 30,
left: 30,
width: origWidth,
height: origHeight,
fill: 'rgba(255,255,255,0)',
strokeWidth: origStrokeWidth,
stroke: '#000000'
});
canvas.add(rect);
canvas.on('object:scaling', onObjectScaled);
function onObjectScaled(e) {
var obj = e.target;
var factorHeight = origHeight / obj.getHeight();
var factorWidth = origWidth / obj.getWidth();
var factor;
if (factorHeight < factorWidth)
factor = factorHeight;
else
factor = factorWidth;
var newStrokeWidth = origStrokeWidth * factor;
obj.setStrokeWidth(newStrokeWidth);
canvas.renderAll();
}

I had the same problem, this is the solution I found somewhere here on SO, but I cant provide you with the link to question, but here is the code that solved it:
canvas.on({
'object:scaling': function(e) {
var obj = e.target;
obj.KeepStrokeWidth = 5;
if(obj.KeepStrokeWidth){
var newStrokeWidth = obj.KeepStrokeWidth / ((obj.scaleX + obj.scaleY) / 2);
obj.set('strokeWidth',newStrokeWidth);
}
}
});
Hope it will work for you too.

Related

Change control fill color while scaling object in Fabricjs

For a few days now, I have been trying to change the fill color of the control that is scaling an object.
Here is a gif of what I'm talking about:
I would like some guidance on how to achieve this. I have been digging through Fabricjs documentation for days trying to get an idea on how to approach this problem.
https://github.com/fabricjs/fabric.js/wiki/Working-with-events
My theory was to bind to mouse:down and mouse:up events. When mouse:down event fires, obtain the control context and change its fill color and when the mouse:up fires, restore the fill color.
Unfortunately, I can't find any fabricjs method that would allow me to obtain the control context.
http://fabricjs.com/docs/fabric.Canvas.html
http://fabricjs.com/docs/fabric.Object.html
canvas.on('mouse:down',(){
// Obtain control context and change fill
});
canvas.on('mouse:up',(){
// Obtain control context and restore fill
});
I'm using Fabricjs version 3.2.0
I overwrote the _drawControl from fabric.Object. As you can see I verified this.__corner control variable, If are equal I changed the fill and stroke color to red. Very important I restored the context after I drawn the control
var canvas = new fabric.Canvas('fabriccanvas');
canvas.add(new fabric.Rect({
top: 50,
left: 50 ,
width: 100,
height: 100,
fill: '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6),
//fix attributes applied for all rects
cornerStyle:"circle",
originX: 'left',
originY: 'top',
transparentCorners:false
}));
fabric.Object.prototype._drawControl = function(control, ctx, methodName, left, top, styleOverride) {
styleOverride = styleOverride || {};
if (!this.isControlVisible(control)) {
return;
}
var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor;
switch (styleOverride.cornerStyle || this.cornerStyle) {
case 'circle':
if(control == this.__corner){
ctx.save();
ctx.strokeStyle = ctx.fillStyle='red';
}
ctx.beginPath();
ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
ctx[methodName]();
if (stroke) {
ctx.stroke();
}
if(control == this.__corner){
ctx.restore();
}
break;
default:
this.transparentCorners || ctx.clearRect(left, top, size, size);
ctx[methodName + 'Rect'](left, top, size, size);
if (stroke) {
ctx.strokeRect(left, top, size, size);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.2.0/fabric.js"></script>
<br/>
<canvas id="fabriccanvas" width="600" height="200" style="border:1px solid #ccc"></canvas>

why is that gl.clear(gl.COLOR_BUFFER_BIT) and requestAnimationFrame will clear all the primitives I drew before

Hi guys I been leanring WebGL and trying to make a Tetris game out of it.
I have a couple of questions I'd like to ask:
For this game I wanted to first draw the grid as the background. However I noticed that after I drew the line, if I use gl.clear(gl.COLOR_BUFFER_BIT ); after, it will clear all the lines I drew before. I understand that gl.clear(gl.COLOR_BUFFER_BIT ); is about clearing the color buffer (and you probably will ask why I would want to do that. Just bear with me. ). Then I tried use gl.uniform4f( uColor, 0, 0, 0, 1); to send the color again to the fragment shader but it doesn't help.
The snippet is like this
window.onload = function(){
getGLContext();
initShaders();
drawLines( 0, 0, 400,400 );
gl.clear(gl.COLOR_BUFFER_BIT );
gl.uniform4f( uColor, 0, 0, 0, 1);
}
For the game I need the grid as background and I need requestAnimationFrame for the game loop and will render Tetrominos inside the loop. Therefore after drawing the line I used this draw() to draw other Tetrominos. However it removes the line I drew before. And when I comment out gl.clear(gl.COLOR_BUFFER_BIT ); inside draw(), it will remove the line along with background color.
function draw() {
gl.clear(gl.COLOR_BUFFER_BIT );
gl.drawArrays(gl.TRIANGLES, 0, index*6);
requestAnimationFrame(draw);
}
Here is the demo: https://codepen.io/zhenghaohe/pen/LqxpjB
Hope you could answer these two questions. Thanks!
This is generally the way WebGL works.
WebGL is just draws into a rectangle of pixels. There is no memory of primitives. There is no structure. There is just code and the resulting canvas which is an rectangle of pixels.
Most WebGL programs/pages clear the entire canvas every frame and redraw 100% of the things they want to show every time they draw. For tetris the general code might be something like
function render() {
clear the canvas
draw the grid
draw all the stable pieces
draw the current piece
draw the next piece
draw the effects
draw the score
}
Any knowledge of primitives or other structure is entirely up to your code.
If you want the grid lines to be static then either set a static background with CSS or use another canvas
Using a background:
const gl = document.querySelector('#c').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
#c {
background-image: url(https://i.imgur.com/ZCfccZh.png);
}
<canvas id="c"></canvas>
Using 2 canvases
// this is the context for the back canvas. It could also be webgl
// using a 2D context just to make the sample simpler
const ctx = document.querySelector('#back').getContext('2d');
drawGrid(ctx);
// this is the context for the front canvas
const gl = document.querySelector('#front').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
function drawGrid(ctx) {
// draw grid
ctx.translate(-10.5, -5.5);
ctx.beginPath();
for (let i = 0; i < 330; i += 20) {
ctx.moveTo(0, i);
ctx.lineTo(330, i);
ctx.moveTo(i, 0);
ctx.lineTo(i, 300);
}
ctx.strokeStyle = "blue";
ctx.stroke();
}
#container {
position: relative; /* required so we can position child elements */
}
#front {
position: absolute;
left: 0;
top: 0;
}
<div id="container">
<canvas id="back"></canvas>
<canvas id="front"></canvas>
</div>
As for why it clears even if you didn't call clear that's because that's whqt the spec says it's supposed to do
See: Why WebGL 'clear' draw to front buffer?

How to keep scaling always at 1 on FabricJS [Edited]

I need in my application made in fabricJS, that when I modify an object with the scaling handlers, the scaling stays in 1, and that the size of the object takes the value of the height multiplied by scaleX. I can apply the same for the width. But I can not apply this in the images. How can I fix this?
I leave an example fiddle for a square, and an image, so that you can see the result.
The result in the image is similar to a crop, and I can not avoid this, because apparently, when I modify the container of the image, the _element property that also contains scaleX and scaleY does not change, so the image is not resized inside the container.
Here is jsFiddle.
var canvas = new fabric.Canvas('canvas');
var rect1 = new fabric.Rect({
width: 100,
height: 100,
left: 200,
top: 200,
angle: 0,
fill: 'rgba(0,0,255,1)',
originX: 'center',
originY: 'center'
});
canvas.add(rect1);
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
//i create an extra var for to change some image properties
var img1 = myImg.set({
left: 0,
top: 0,
width: 150,
height: 150
});
canvas.add(img1);
});
canvas.on('mouse:up', function(e) {
/*if(e.target != null){
//alert(e.target.width*e.target.scaleX);
}*/
//console.log(e.target);
canvas.getActiveObjects().forEach(function(obj) {
//if(obj.get('type')!='image'){
obj.set('width', obj.width * obj.scaleX, obj);
obj.set('height', obj.height * obj.scaleY, obj);
//if(obj.get('type')!='image'){
obj.scaleX = 1;
obj.scaleY = 1;
//}else{
// obj.scaleX = 1;
// obj.scaleY = 1;
// obj._element.scaleX = 1;
// obj._element.scaleY = 1;
// console.log("Width Contenedor Imagen: "+obj.width+" Width Imagen: "+obj._element.width);
// console.log("Scale Contenedor Imagen: "+obj.scaleX+" Scale Imagen: "+obj._element.scaleX);
//}
obj.setCoords();
//}
});
});
EDIT: New Discovery
Speaking of text, rect, images and polylines, it seems that I am modifying an external container, because the inner content remains intact. How can I modify the size of that let's say, inner container?

set Min and Max resize limits for fabric object

I am using fabric js for resizing object i want user to resize the object with in min&max limits. How to do this with fabric js.
I tried properties like
lockScalingX,lockScalingY,lockMomentX,lockMomentY but no luck.
Any help will be grateful.
Thanks,
There is no way to do it natively in fabric but you can hook in to the scaling event and make any modification to the object you should like. In this code I stop the scaling as well as correct fabric from shifting the top/left when I am over riding the scaling.
window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
});
canvas.add(rect);
var maxScaleX = 2;
var maxScaleY = 2;
rect.on('scaling', function() {
if(this.scaleX > maxScaleX) {
this.scaleX = maxScaleX;
this.left = this.lastGoodLeft;
this.top = this.lastGoodTop;
}
if(this.scaleY > maxScaleY) {
this.scaleY = maxScaleY;
this.left = this.lastGoodLeft;
this.top = this.lastGoodTop;
}
this.lastGoodTop = this.top;
this.lastGoodLeft = this.left;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Might come in handy for people searching for similar answers.
You can natively set the minimum limit for scaling by setting minScaleLimit. However, there isn't an out-of-the-box solution available for setting maximum limit.
i addapted the drag limit algorithm that i had. In my specific case, i have a canvas element with a background image and i had to limit the other objects resizing with the background image limits so i added extra margins to do that. But if you need to limit the objects resizing only with the canvas size you can set the extra margins to 0.
//Limit the draggable zone
this.canvas.on("object:scaling", function (e) {
let obj = e.target;
let canvas = obj.canvas;
let zoom = canvas.getZoom();
let pan_x = canvas.viewportTransform[4];
let pan_y = canvas.viewportTransform[5];
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
let canvas_height = canvas.height / zoom;
let canvas_width = canvas.width / zoom;
let totalWidth = obj.width * obj.scaleX;
let totalHeight = obj.height * obj.scaleY;
// if you need margins set them here
let top_margin = marginYTop;
let bottom_margin = marginYBottom;
let left_margin = marginXLeft;
let right_margin = marginXRight;
let top_bound = top_margin - pan_y;
let bottom_bound = canvas_height - bottom_margin - pan_y;
let left_bound = left_margin - pan_x;
let right_bound = canvas_width - right_margin - pan_x;
if (obj.top < top_bound || (obj.top + totalHeight) > bottom_bound) {
obj.scaleY = obj.canvas.lastScaleY;
obj.set("top", top_bound);
}
if (obj.left < left_bound || (obj.left + totalWidth) > right_bound) {
obj.scaleX = obj.canvas.lastScaleX;
obj.set("left", left_bound);
}
obj.canvas.lastScaleY = obj.scaleY;
obj.canvas.lastScaleX = obj.scaleX;
});
As this is top 1 topic in google:
Better answer (based on StefanHayden snipped ^^ and http://jsfiddle.net/fabricjs/58y8b/):
window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
});
//Gradient to see pattern on passing 0 in scaling
// horizontal linear gradient
rect.setGradient('fill', {
type: 'linear',
x1: -rect.width / 2,
y1: 50,
x2: rect.width / 2,
y2: 50,
colorStops: {
0: '#ffe47b',
1: 'rgb(111,154,211)'
}
});
canvas.add(rect);
var maxScaleX = 2;
var maxScaleY = 2;
//Set starting center point:
var centerPoint = rect.getCenterPoint();
//Save center point on center point changing events (moved, maybe some cases of: rotated (not center rotation), added and drop (just assuming for last two))
rect.on('moved rotated added drop', function() {
centerPoint = rect.getCenterPoint();
});
rect.on('scaling', function() {
if(this.scaleX > maxScaleX) {
this.scaleX = maxScaleX;
}
if(this.scaleY > maxScaleY) {
this.scaleY = maxScaleY;
}
rect.setPositionByOrigin(centerPoint, 'center', 'center');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>

In createjs textAlign Center Causing hit area misalignment

When I set up text as a link, set a bounds shape, and set the hit area to the bounds shape, my hit area is off if the textAlign = 'center'. Any ideas?
var linkColor = "#0000ff";
var linkFont = "bold 14px Times";
var presentationLink = new createjs.Text("View Presentation", linkFont, linkColor);
presentationLink.textAlign = "left";
presentationLink.maxWidth = 170;
presentationLink.x = 300;
presentationLink.y = 125;
stage.addChild(presentationLink);
var plBounds = presentationLink.getTransformedBounds();
var s = new createjs.Shape().set({ x: plBounds.x, y: plBounds.y + plBounds.height });
s.graphics.s(linkColor).moveTo(0, 0).lineTo(plBounds.width, 0);
stage.addChild(s);
var hitAreaForPLink = new createjs.Shape(new createjs.Graphics().beginFill("#ffffff").drawRect(-10, -10, plBounds.width + 20, plBounds.height + 10));
presentationLink.hitArea = hitAreaForPLink;
stage.enableMouseOver();
presentationLink.on("mouseover", function () {
presentationLink.cursor = "pointer";
});
The hitArea is positioned according to its owner's coordinate system. That is, all of the owner's transformations are applied to the hitArea. This is so that if you were to animate the owner, the hitArea would track it as expected.
Since the transformation is already applied, you need to use getBounds, not getTransformedBounds. See this example: http://jsfiddle.net/gskinner/uagv5t84/2/

Resources