I'am trying to clip the FabricJS rect shape to the polygon shape. The clipping works okay until the polygon shape which need to be clipped is now scaled. After this there is some weird offset that is caused by the polygon clipping.
Can anyone help me how can i fix the function to prevent the polygon offset issue when clip object is scaled.
This is how it looks before scalling. The clipping works fine
Image => https://i.imgur.com/Eop2YJh.png
And then there is the problem when the polygon is scaled.
2: Image => https://i.imgur.com/ICkP8SG.png
Here is the code on fiddle with the clipping function
https://jsfiddle.net/0xpvc9uq/
So if there is anyone who knows whats the point and how can I fix it I would appriciate it.
Thx
var canvas = new fabric.Canvas('c');
var rect1 = new fabric.Rect({
left: 0, top: 0,
width: 900, height: 900,
fill: 'blue',
selectable: false,
clipTo: clipRegion,
scaleX: 1.5,
scaleY: 1.5
});
var clipPoly = new fabric.Polygon([
{ x: 180, y: 10 },
{ x: 300, y: 50 },
{ x: 300, y: 180 },
{ x: 180, y: 220 }
], {
originX: 'left',
originY: 'top',
left: 180,
top: 10,
fill: 'transparent', /* use transparent for no fill */
strokeWidth: 0,
selectable: false,
strokeWidth: 1,
stroke: "red",
scaleX: 1.3,
scaleY: 1.3
});
canvas.add(rect1, clipPoly);
function clipRegion (ctx) {
rect1.setCoords();
const clipObj = clipPoly;
const scaleXTo1 = (1 / rect1.scaleX);
const scaleYTo1 = (1 / rect1.scaleY);
ctx.save();
const ctxLeft = -( rect1.width / 2 ) - clipObj.strokeWidth - rect1.strokeWidth;
const ctxTop = -( rect1.height / 2 ) - clipObj.strokeWidth - rect1.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.rotate((rect1.angle * -1) * (Math.PI / 180));
ctx.beginPath();
const matrix = clipPoly.calcTransformMatrix();
let points = [];
clipObj.points.forEach( (point) => {
points.push({
x: ((point.x * matrix[0]) + (clipObj.strokeWidth * clipObj.scaleX)) - rect1.oCoords.tl.x,
y: ((point.y * matrix[3]) + (clipObj.strokeWidth * clipObj.scaleY)) - rect1.oCoords.tl.y
});
});
ctx.moveTo(points[0].x, points[0].y);
points.forEach((point) => {
ctx.lineTo(point.x, point.y);
});
ctx.lineTo(points[0].x, points[0].y);
ctx.closePath();
ctx.restore();
}
I discovered that there is also another approach that can be used and that solves all the problem.
The clipRegion function now looks like:
function clipRegion (ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
clipPoly.render(ctx);
ctx.restore();
}
Which makes the rendering okay. If still anyone have other way to fix the above problem, I would like to see the answer
Related
I am new to Lottie and am wondering how to play 2 animations side by side at the same time in the same canvas element on the Web.
I have followed this example[1] and then the advice given here[2] with regards to transforming the canvas on every frame to position either animation respectively.
What I would like to achieve is this: 2 red balls bouncing side by side on the same canvas. One playing at x = 0 and one playing at x = 100 on the same canvas.
Here is my approach in a CodePen[3]. I extracted the JS for visibility.
const testjson = {...animation...};
const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
// Drawing sth on the context to see whether sharing works
ctx.fillStyle = "green";
ctx.fillRect(0, 0, 40, 40);
function renderAnimation(canvasContext, translation) {
const animData = {
renderer: "canvas",
loop: true,
rendererSettings: {
context: canvasContext,
clearCanvas: true,
},
animationData: testjson,
};
const anim = bodymovin.loadAnimation(animData);
// Transform the canvas for the respective animation on enter-frame
anim.addEventListener("enterFrame", () => {
ctx.translate(translation.x, translation.y);
});
// If efective, causes onion-effect
// anim.setSubframe(false);
}
renderAnimation(ctx, { x: 0, y: 0 });
renderAnimation(ctx, { x: 100, y: 0 });
Alas, the way I implemented it does not seem to work.
Does anybody know what I am missing?
Thank you!
[1]: https://codepen.io/RenanSgorlom/pen/orgxyJ
[2]: https://github.com/airbnb/lottie-web/issues/1671
[3]: https://codepen.io/user1207504/pen/MWVYvxd
You don't need that much code to have some balls bouncing on the same canvas ...
Certainly no need for a library to just do that.
The balls we can draw using arc functions:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc
movement is to increase the position on x or y, in the code is the x += vx
bouncing we just change the direction, you can see it in my code vx *= -1
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
class ball {
constructor(data) {
this.data = data
}
draw() {
this.data.x += this.data.vx
this.data.y += this.data.vy
if (this.data.x > canvas.width || this.data.x < 0) this.data.vx *= -1
if (this.data.y > canvas.height || this.data.y < 0) this.data.vy *= -1
ctx.beginPath()
ctx.fillStyle = this.data.color
ctx.arc(this.data.x,this.data.y, this.data.radius, 0, 2*Math.PI);
ctx.fill()
}
}
const balls = [
new ball({x: 10, y: 10, vx: 0, vy: 1, radius: 8, color: "pink" }),
new ball({x: 90, y: 90, vx: 0, vy: -1, radius: 8, color: "red" }),
new ball({x: 5, y: 50, vx: 1, vy: 1.5, radius: 8, color: "cyan" })
]
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
balls.forEach(b => b.draw())
requestAnimationFrame(animate)
}
animate()
<canvas id="canvas" width=100 height=100></canvas>
I have following example stage.
const rectangle = new Konva.Rect({
width: 300,
height: 100,
x: stage.width() / 2,
y: stage.height() / 2,
fill: 'blue',
rotation: 140
});
layer.add(rectangle);
layer.draw();
const tooltip = new Konva.Label({
x: rectangle.x() + (rectangle.width() / 2),
y: rectangle.y() + (rectangle.height() / 2),
opacity: 0.75
});
tooltip.add(
new Konva.Tag({
fill: 'green',
pointerDirection: 'down',
pointerWidth: 6,
pointerHeight: 4,
lineJoin: 'round'
})
);
tooltip.add(new Konva.Text({
text: 'Hello world',
fontSize: 12,
padding: 5,
fill: 'white',
}));
layer.add(tooltip);
layer.draw();
When the rectangle has no rotation I am able to place the tooltip correctly in the centre of the node. When the rectangle has some rotation the tooltip is not centred any more.
Without changing the attributes of the rectangle or rotating the tooltip, how can I place the tooltip in the centre of the rectangle node?
JSBIN example:
https://jsbin.com/wopoxuligi/edit?html,js,output
First, you need to update your declaration for Konva in the html script tag as you are referencing an old version.
<script src="https://unpkg.com/konva#7.0.3/konva.min.js"></script>
Because this shape is a rect you can call rectangle.getClientRect() to get the position on the stage, then use simple center-math to place your label. Slip this code in at the end of your current code where you do the last layer.draw();
const cliRect = rectangle.getClientRect();
let pos = {
x: cliRect.x + (cliRect.width )/2,
y: cliRect.y + (cliRect.height )/2
};
tooltip.x(pos.x)
tooltip.y(pos.y)
layer.draw();
This will leave the pointer of the label pointing at the centre of the rotated rectangle, with the label being non-rotated. Working snippet below - run it full screen.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
draggable: true
});
const layer = new Konva.Layer();
stage.add(layer);
const rectangle = new Konva.Rect({
width: 300,
height: 100,
x: stage.width() / 2,
y: stage.height() / 2,
fill: 'blue',
rotation: 140
});
layer.add(rectangle);
layer.draw();
const tooltip = new Konva.Label({
x: rectangle.x() + (rectangle.width() / 2),
y: rectangle.y() + (rectangle.height() / 2),
opacity: 0.75
});
tooltip.add(
new Konva.Tag({
fill: 'green',
pointerDirection: 'down',
pointerWidth: 6,
pointerHeight: 4,
lineJoin: 'round'
})
);
tooltip.add(new Konva.Text({
text: 'Hello world',
fontSize: 12,
padding: 5,
fill: 'white',
}));
layer.add(tooltip);
const cliRect = rectangle.getClientRect();
// Extra red rect to illustrate client rect. Not needed in solution
const rectangle1 = new Konva.Rect({
width: cliRect.width,
height: cliRect.height,
x: cliRect.x,
y: cliRect.y,
stroke: 'red'
});
layer.add(rectangle1);
let pos = {
x: cliRect.x + (cliRect.width )/2,
y: cliRect.y + (cliRect.height )/2
};
// Set tooltip position taking account of stage draggind
tooltip.x(pos.x - stage.x())
tooltip.y(pos.y - stage.y())
console.log(pos)
layer.draw();
<script src="https://unpkg.com/konva#7.0.3/konva.min.js"></script>
<div id="container"></div>
EDIT: In a comment the OP pointed out that the real use case requires the stage to be draggable before the label is added and that after the drag the calc of the label position fails. This is because getClientRect() gives the position in the client container without adjustment for the stage movement. To make the code work with a dragged stage, change the two lines that set tooltip.x & y to adjust by the stage.x & y, as follows:
tooltip.x(pos.x - stage.x())
tooltip.y(pos.y - stage.y())
I added offset to rectangle, to rotate it by its center point and managed to have the tooltip centered.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
var group1 = new Konva.Group({
draggable: true
});
const rectWidth = 300;
const rectHeight = 100;
const rectangle = new Konva.Rect({
width: rectWidth,
height: rectHeight,
x: stage.width() / 2,
y: stage.height() / 2,
fill: 'blue',
rotation: 140,
offset: {
x: rectWidth / 2,
y: rectHeight / 2,
},
});
const tooltip = new Konva.Label({
x: rectangle.x() + (rectangle.width() / 2),
y: rectangle.y() + (rectangle.height() / 2),
offset: {
x: rectangle.width() / 2,
y: rectangle.height() / 2,
},
opacity: 0.75
});
tooltip.add(
new Konva.Tag({
fill: 'green',
pointerDirection: 'down',
pointerWidth: 6,
pointerHeight: 4,
lineJoin: 'round'
})
);
tooltip.add(new Konva.Text({
text: 'Hello world',
fontSize: 12,
padding: 5,
fill: 'white',
}));
layer.add(rectangle);
layer.add(tooltip);
layer.draw();
<script src="https://unpkg.com/konva#^2/konva.min.js"></script>
<div id="container"></div>
I know you can draw a wedge using a Kinetic.Wedge:
var compassArc = new Kinetic.Wedge({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 70,
angleDeg: 60,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
rotationDeg: -90
});
This draws a "pizza slice" with a black outline around the whole thing. I just want the "crust" of the pizza, with no straight lines coming back to the center of the circle. How can I do this?
Setting the fill to null removes the red but leaves the outline.
How about creating a custom shape fot this using arc?
http://www.html5canvastutorials.com/tutorials/html5-canvas-arcs/
Please keep in mind that not to close path and not to fill strokes. if so, you will get what you want. It is a KineticJS object, so that you can drag around if you want.
Here is the working example.
http://jsfiddle.net/bighostkim/WzxxH/
var arc = new Kinetic.Shape({
drawFunc: function(canvas) {
var context = canvas.getContext();
var x = stage.getWidth() / 2;
var y = stage.getHeight()/2;
var radius = 70;
var startAngle = 1 * Math.PI;
var endAngle = 0 * Math.PI;
var context = canvas.getContext('2d');
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, false);
//context.closePath();
canvas.stroke(this);
},
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
draggable:true
});
allenhwkim answer is a bit outdated and has some problems. For example the kinetic dash array would not work. So here is a revised version:
var arc = new Kinetic.Shape({
x: 100,
y: 100,
stroke: '#000',
strokeWidth: 4,
dash: [8, 4],
drawFunc: function(context) {
var radius = 50;
var startAngle = 1 * Math.PI;
var endAngle = 0 * Math.PI;
context.beginPath();
context.arc(0, 0, radius, startAngle, endAngle, false);
context.fillStrokeShape(this);
},
draggable:true
});
There is a Kinetic.Arc class which you can use.
Make outerRadius equal to innerRadius and you will get what you want.
this.arc = new Kinetic.Arc({
innerRadius: 90,
outerRadius: 90,
stroke: 'red',
strokeWidth: 2,
angle: 60,
rotationDeg: 210
});
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.
I'm using the KineticJS library to draw some images to the canvas, and make them draggable. I've downloaded a copy of the library, so that I can make one or two slight adjustments to it, in order to use it with the canvas game I'm making.
The function within the KineticJS library that I've altered is the
drawHit: function(){}
Originally, the function looked like this:
drawHit: function() {
this.hitCanvas.clear();
Kinetic.Container.prototype.drawHit.call(this);
},
But I've added in a couple of calls to functions that I've written:
drawHit: function() {
this.hitCanvas.clear();
drawGameElements();
drawDescriptionBoxes(imageObj);
Kinetic.Container.prototype.drawHit.call(this);
},
The reason I've done this, is because beforehand, when dragging and dropping an image around the canvas, the canvas would be completely cleared when a click was detected, and only the images would be redrawn- I had a few other things displayed on the canvas that I wanted to remain there each time the canvas was redrawn when moving an image around. (These things were drawn by the two function calls I've added to this function)
However, when I view my page in a browser now, I'm getting the Firebug console error:
ReferenceError: drawDescriptionBoxes is not defined
drawDescriptionBoxes(imageObj);
on the line
drawDescriptionBoxes(imageObj);
(where I've added it to the function)
The function 'drawDescriptionBoxes' is defined in a file called 'drawdescriptionboxes.js' and has the following code:
function drawDescriptionBoxes(imageObj){
/* CanvasRenderingContext2D.prototype.drawDescriptionArea = function(x, y, width, height, radius, stroke){
if(typeof stroke == "undefined" ){
stroke = true;
}
if(typeof radius === "undefined"){
radius = 5;
}
this.beginPath();
this.moveTo(x + radius, y);
this.lineTo(x + width - radius, y);
this.quadraticCurveTo(x + width, y, x + width, y + radius);
this.lineTo(x + width, y + height - radius);
this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
this.lineTo(x + radius, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - radius);
this.lineTo(x, y + radius);
this.quadraticCurveTo(x, y, x + radius, y);
this.closePath();
if(stroke){
context.stroke();
}
} */
var assetsDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 70,
y: 400
draggable: false
});
var liabilitiesDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 300,
y: 400
draggable: false
});
var incomeDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 540,
y: 400
draggable: false
});
var expenditureDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 750,
y: 400
draggable: false
});
}
I am calling this function from the
window.onload = function(){}
in my index.html page with the line
drawdescriptionboxes();
I have another couple of functions also being called here in exactly the same way, and there is no problem with them.
Anyone have any ideas why I'm getting this reference error?
Edit
I'm also getting a syntax error: missing } after property list on line 30 of drawDescriptionBoxes.js:
function drawDescriptionBoxes(imageObj){
/* CanvasRenderingContext2D.prototype.drawDescriptionArea = function(x, y, width, height, radius, stroke){
if(typeof stroke == "undefined" ){
stroke = true;
}
if(typeof radius === "undefined"){
radius = 5;
}
this.beginPath();
this.moveTo(x + radius, y);
this.lineTo(x + width - radius, y);
this.quadraticCurveTo(x + width, y, x + width, y + radius);
this.lineTo(x + width, y + height - radius);
this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
this.lineTo(x + radius, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - radius);
this.lineTo(x, y + radius);
this.quadraticCurveTo(x, y, x + radius, y);
this.closePath();
if(stroke){
context.stroke();
}
} */
var assetsDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 70,
y: 400
draggable: false
});
var liabilitiesDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 300,
y: 400
draggable: false
});
var incomeDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 540,
y: 400
draggable: false
});
var expenditureDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 750,
y: 400
draggable: false
});
context.drawImage(sources[34], 70, 400, 120, 70);
context.drawImage(sources[35], 300, 400, 120, 70);
context.drawImage(sources[36], 540, 400, 120, 70);
context.drawImage(sources[37], 750, 400, 120, 70);
}
Line 30 is the line:
draggable: false
in
var assetsDescriptionBoxImage = new Kinetic.Image({
image: imageObj,
width: 120,
height: 70,
x: 70,
y: 400
draggable: false
});
so I don't know if this is what would be causing the other error?
using developer tools in Chrome or firebug in firefox (F12) set a break point in your script and follow the function calls step by step and make sure your library is included and called (function is defined) before you call it.
You should try copy/paste your function definition drawdescriptionboxes just before calling it first time, once it works fine, you should place it in a place where the function definition gets called before the function call.