fabric.js limit rotation to x degrees with rotation handle - javascript

I am using fabric.js and trying to allow rotation for any object on my canvas, not freely 360 degrees, but only 15 degrees at a time, using the rotation handle. I searched really hard but couldn't find an answer so far. Is this possible?

Shorter solution:
canvas.on('object:rotating', function(options) {
var step = 15;
options.target.angle = Math.round(options.target.angle / step) * step;
});
UPD: since 1.6.7 you can just use fabric.Object.snapAngle property:
someFabricObject.snapAngle = 15;

You can use canvas.on("object:rotating") to interfer with rotation of all objects.
I defined a set of valid angles, and then on the rotation event i checked which one to use.
var angles = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345, 360];
canvas.on("object:rotating", function(rotEvtData) {
var targetObj = rotEvtData.target;
var angle = targetObj.angle % 360;
for (var i=0; i < angles.length; i++) {
if (angle <= angles[i]) {
targetObj.angle = angles[i];
break;
}
}
});
var canvas = new fabric.Canvas("c");
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 90,
height: 90
});
canvas.add(rect);
var angles = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345, 360];
canvas.on("object:rotating", function(rotEvtData) {
var targetObj = rotEvtData.target;
var angle = targetObj.angle % 360;
for (var i=0; i < angles.length; i++) {
if (angle <= angles[i]) {
targetObj.angle = angles[i];
break;
}
}
});
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>

Related

canvas use more ctx element

My task is to create two canvases (big and small). In big one create 5 stars of different colors.
Task: by clicking on the star, change the color of the small canvas to the color of the star. Now the problem is that addEventListener only works on the last element. Any ideas?
.html template:
<canvas style="background-color:rgb(255, 255, 255); border: 1px solid black" id='big'>Обновите браузер</canvas>
<canvas style="background-color:rgb(255, 255, 255); border: 1px solid black" id='small'>Обновите браузер</canvas>
.js script:
const big = document.getElementById("big");
const small = document.getElementById("small");
big.height = 600;
big.width = 600;
small.height = 600;
small.width = 50;
function createStar(moveToX, moveToY, lineToX1, lineToY1, lineToX2, lineToY2, lineToX3, lineToY3, lineToX4, lineToY4, color) {
ctx = big.getContext('2d');
ctx.beginPath();
ctx.moveTo(moveToX, moveToY);
ctx.lineTo(lineToX1, lineToY1);
ctx.lineTo(lineToX2, lineToY2);
ctx.lineTo(lineToX3, lineToY3);
ctx.lineTo(lineToX4, lineToY4);
ctx.closePath();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fill();
ctx.stroke();
};
const red = new createStar(20, 60, 100, 60, 35, 110, 60, 25, 85, 110, 'red');
const blue = new createStar(120, 60, 200, 60, 135, 110, 160, 25, 185, 110, 'blue');
const green = new createStar(120, 160, 200, 160, 135, 210, 160, 125, 185, 210, 'green');
const black = new createStar(220, 460, 400, 460, 235, 560, 300, 400, 385, 560, 'black');
const yellow = new createStar(220, 260, 300, 260, 235, 310, 260, 225, 285, 310, 'yellow');
big.addEventListener('click', function(e){
if(ctx.isPointInPath(e.offsetX, e.offsetY)) {
small.style.backgroundColor = 'red';
} else {
small.style.backgroundColor = 'white';
}
});
context.isPointInPath(x, y) will check if the point at coords x and y is inside the current sub-path that's being traced in the context.
Every time you call ctx.beginPath(), this sub-path is cleared.
To workaround that you would have to retrace the whole list of stars and check one by one if the point falls in there.
However, there is also a Path2D interface that you can use.
This interface allows to hold such sub-path as their own object, and can be used by the context instead of the internal context's path by e.g ctx.fill(path), ctx.stroke(path), ctx.clip(path), ctx.scrollPathIntoView(path), ctx.drawFocusIfNeeded(path, element), and you probably guessed it, ctx.isPointInPath(path, x, y), or ctx.isPointInStroke(path, x, y).
So you can rewrite your code so that each star is stored in its own Path2D instance, store all these instances in an Array and then traverse that Array to check if any of these was hit by the point:
const stars = [];
const big = document.getElementById("big");
const small = document.getElementById("small");
big.height = 600;
big.width = 500;
small.height = 600;
small.width = 50;
const ctx = big.getContext("2d");
function createStar(moveToX, moveToY, lineToX1, lineToY1, lineToX2, lineToY2, lineToX3, lineToY3, lineToX4, lineToY4, color) {
const path = new Path2D();
// store our path in an object also hodling the color
stars.push({path, color});
path.moveTo(moveToX, moveToY);
path.lineTo(lineToX1, lineToY1);
path.lineTo(lineToX2, lineToY2);
path.lineTo(lineToX3, lineToY3);
path.lineTo(lineToX4, lineToY4);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fill(path);
ctx.stroke(path);
};
const red = new createStar(20, 60, 100, 60, 35, 110, 60, 25, 85, 110, 'red');
const blue = new createStar(120, 60, 200, 60, 135, 110, 160, 25, 185, 110, 'blue');
const green = new createStar(120, 160, 200, 160, 135, 210, 160, 125, 185, 210, 'green');
const black = new createStar(220, 460, 400, 460, 235, 560, 300, 400, 385, 560, 'black');
const yellow = new createStar(220, 260, 300, 260, 235, 310, 260, 225, 285, 310, 'yellow');
big.addEventListener('click', function(e) {
// find the first star that got clicked
const touched = stars.find((star) =>
ctx.isPointInPath(star.path, e.offsetX, e.offsetY)
);
if (touched) {
small.style.backgroundColor = touched.color;
} else {
small.style.backgroundColor = 'white';
}
});
canvas {
background-color: rgb(255, 255, 255);
border: 1px solid black;
}
<canvas id='big'>Обновите браузер</canvas>
<canvas id='small'>Обновите браузер</canvas>

Corridor path by line path and width

I need to write a function, which generates corridor path by line path and width.
For example, I have array of coords of polyline:
var coords = [
50, 50,
150, 50,
250, 100,
220, 200,
350, 100,
];
https://i.stack.imgur.com/FCCxb.png
Then I use function to get corridor path:
var width = 10;
var corridorPath = getCorridorPath(coords, width);
Function should return something like that:
[
40, 50,
50, 40,
150, 40,
157, 41,
255, 90,
260, 105,
240, 170,
340, 90,
360, 90,
360, 110,
220, 215,
205, 205,
233, 107,
147, 62,
50, 60,
40, 50,
]
https://i.stack.imgur.com/v3wxN.png
Calculate parallel polylines and conjunctions.
For two neighbor edges AB and BC find normalized vectors ab and cb.
Calculate unit bisector vector:
b = (ab + cb).normalized
Calculate length of bisector segments as
len = d / sin(fi)
where d is offset (halfwidth), and fi is angle between vectors b and ab:
fi = atan2(crossproduct(b,ab), dotproduct(b,ab))
Find offset points (left and right ones) as
B' = B + l * b
B'' = B - l * b
Note that you want to cut long triangles, so offset point is valid for side with acute angle, and you need to recalculate two points for cut cap - for example, get central point B''' = B - d * b and add vectors, perpendicular to bisector.

How to make drawImage position a variable inside canvas

The problem is that the "position" variable is not beeing updated in order to make the digit display next to each other and not on top of each other.
I also tryed to put it inside the prototype function thinking that if it's beeing updated right before it runs the drawImage function it will work...but it does not and I do not understand what the problem is.
<pre>
var sprite = new Image();
sprite.src = "Flappy_Bird_Sprite.png"
function drawSprite (img, x_crop, y_crop, x_width, y_height, x_pos, y_pos, x_posWidth, y_posHeight) {
this.img = img;
this.x_crop = x_crop;
this.y_crop = y_crop;
this.x_width = x_width;
this.y_height = y_height;
this.x_pos = x_pos;
this.y_pos = y_pos;
this.x_posWidth = x_posWidth;
this.y_posHeight = y_posHeight;
}
var position = 10;
drawSprite.prototype.draw = function () {
ctx.drawImage(this.img, this.x_crop, this.y_crop, this.x_width, this.y_height, this.x_pos, this.y_pos, this.x_posWidth, this.y_posHeight);
}
var arr = [
new drawSprite(sprite, 137, 306, 7, 10, position, 50, 20, 40),
new drawSprite(sprite, 139, 477, 5, 10, position, 50, 15, 40),
new drawSprite(sprite, 292, 160, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 306, 160, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 320, 160, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 334, 160, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 292, 184, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 306, 184, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 320, 184, 12, 18, position, 50, 15, 40),
new drawSprite(sprite, 334, 184, 12, 18, position, 50, 15, 40),
]
var increment = 0;
function animate () {
requestAnimationFrame(animate);
increment++;
if (increment % 10 == 0){
score++;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
var tString = score.toString(); //Makeing the score into a string so I can select each number with the substring function
var length = tString.length; //Finding out the length of the string(in our case is 2)
for(var i = 0; i < length; i++){
var substring = tString.substring(i,i+1); //Selecting each digit of that number in order to draw each of them separately
var toNumber = parseInt(substring); //Setting that "string" back into a number so I can acces that number in the array
if( length == 2 ) { //I am trying to make this work for 2 digit number(10-99) and if this works, then I will add 3 digits and so on, I know that this should work only for numbers from 10-99
if( i == 0){ // Position the first digit of that number at the position ....
position = 180;
}
if( i == 1){ // Position the second digit of that number at the position ....
position = 105;
}
}
arr[toNumber].draw();
}
}
animate();
<code>
Your calls to new drawSprite all use the same value for position, which is fixed at the point you create those objects.
You could try updating the sprite instance before drawing:
arr[toNumber].x_pos = position;
arr[toNumber].draw();

Use canvas drawImage to separate img into two halves

I'm trying to split image in my canvas.
First I'm declaring canvas in HTML:
<canvas width="600" height="400" id="canvas_1">
Canvas tag not supported
</canvas>
Then I'm unsuccesfully spliting my image:
var canvas = document.getElementById("canvas_1");
if (canvas.getContext){
var canvas_context = canvas.getContext("2d");
var img = document.getElementById("london_eye");
canvas_context.drawImage(img, 0, 0, 230, 300, 20, 20, 80, 300);
canvas_context.drawImage(img, 30, 0, 180, 300, 200, 20, 80, 300);
}
I guess I'm missing something there..
canvas_context.drawImage(img, 0, 0, 230, 300, 20, 20, 80, 300);
canvas_context.drawImage(img, 30, 0, 180, 300, 200, 20, 80, 300);
FIDDLE
Thank you for your time and advices
Original Code
var canvas = document.getElementById("canvas_1");
if (canvas.getContext){
var canvas_context = canvas.getContext("2d");
var img = document.getElementById("london_eye");
canvas_context.drawImage(img, 0, 0, 60, 120, 0, 0, 60, 120);
canvas_context.drawImage(img, 60, 0, 60, 120, 70, 0, 60, 120);
}
The last four parameters are destX, destY, destWidth, destHeight. So this is where on the canvas you want to put these pieces, you can see second piece is at 70 so its width of first piece 60 plus gap of 10.
I put a gap of 10px to show the two pieces of your img in the snippet!
var i = new Image();
i.crossOrigin = '';
i.onload = function() {
var canvas = document.getElementById("canvas_1");
if (canvas.getContext){
var canvas_context = canvas.getContext("2d");
canvas_context.drawImage(i, 0, 0, 60, 120, 0, 0, 60, 120);
canvas_context.drawImage(i, 60, 0, 60, 120, 70, 0, 60, 120);
}
};
i.onerror = function() {
i.src = 'http://cors.io?u=' + i.src;
i.onerror = function() {
document.write('Error loading image');
}
};
i.src = 'https://i.stack.imgur.com/olfBw.png';
<canvas id="canvas_1"></canvas>

Canvas triangle without linejoin and stroke width

I have this triangle that I have rounded corners on but I'm using the arcTo:
context.moveTo(140, 0);
context.arcTo(180, 100, 100, 100, 4);
context.arcTo(100, 100, 140, 0, 4);
context.arcTo(140, 0, 180, 100, 4);
As you will see the top angle looks a bit messed up. Any ideas how to fix it? Seems like there needs to be some calculations for the initial moveTo(x, y) but 140, 0 is where it should start.
I just got rid of the moveTo and arced them to eachother. Started the first part at 174, 176 (180-4 radius) works but 174 had no overlap at all.
Live Demo
​var canvas = document.getElementsByTagName("canvas")[0],
ctx = canvas.getContext("2d");
canvas.width = canvas.height = 400;
ctx.beginPath();
ctx.arcTo(174, 100, 100, 100, 4);
ctx.arcTo(100, 100, 140, 0, 4);
ctx.arcTo(140, 0, 180, 100, 4);
ctx.arcTo(180, 100, 100, 100, 4);
ctx.stroke();
​
Here is what I came up with:
var r = 8;
var offset = (6 * r / 4) - 1;
context.arcTo((180 - offset), 100, 100, 100, r);
context.arcTo(100, 100, 140, 0, r);
context.arcTo(140, 0, 180, 100, r);
context.arcTo(180, 100, 100, 100, r);
Using part of what Loktar provided, I've modified it slightly to use a ratio of what I know works for a given diameter, and used this as the offset. With a diameter of 4, I know 6 works.
Seems like there should be a better way but I'm happy with this.

Categories