Basically, if I use stage.onFrame(function(frame){animationLayer.draw()}); then I get a jerky animation, but if I do something like setInterval(draw, 25); and then animationLayer.draw(); in draw, then I get a nice smooth animation.
Am I doing something wrong with KineticJS or is it just a bit sucky on the performance? I was only spinning a rectangle but it appears so jerky.
It's worse in chrome than in firefox but firefox still isn't completely smooth.
Anyone have any ideas why?
var debug, stage, animationLayer;
var sw, sh;
var spinRect;
var blobArray = [];
window.onload = function() {
debug = document.getElementById('debug');
stage = new Kinetic.Stage({container: "kineticdiv", width: 700, height: 400});
animationLayer = new Kinetic.Layer();
sw = stage.attrs.width;
sh = stage.attrs.height;
spinRect = new Kinetic.Rect({
x: sw/4*3,
y: sh/2,
width: 50,
height: 50,
fill: "#eee",
stroke: "#777",
strokeWidth: 2,
centerOffset: {
x: 25,
y: 25
}
});
var centerRect = new Kinetic.Rect({
x: sw/4-5,
y: sh/2-5,
width: 10,
height: 10,
fill: "cyan",
stroke: "black",
strokeWidth: 2
});
animationLayer.add(spinRect);
animationLayer.add(centerRect);
stage.add(animationLayer);
setInterval(update, 25); // 33 ms = 30 fps, 25 ms = 40 fps
stage.onFrame(function(frame){animationLayer.draw()});
stage.start();
};
function update()
{
spinRect.rotate(0.03); //Math.PI / 100); // even removed this for less calculations
// animationLayer.draw() // smoother if I use this instead
}
Thanks
Edit: Turns out that Chrome is to blame for some of the issues here, a recent update has been causing some trouble.
v3.9.4 will be released later today which has some significant animation and transition improvements. Is this animation smooth for you?
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-animate-position-tutorial/
Also, animations can be jerky if you have lots of other stuff running at the same time. Check out this example that uses the requestAnimFrame and see if this is smooth or not (pure JS, no library):
http://paulirish.com/2011/requestanimationframe-for-smart-animating/
Related
I'm new to fabricjs (and to Javascript development in general). I am "porting" a legacy Flex/Actionscript project and need to enable the user to create a complex clipping path for an image.
My approach in Actionscript was to use the Actionscript Graphics class using BlendMode.ERASE to "erase" from the yellow base rectangle (i.e. give the appearance of erasing) and then using that set of rects to create a bitmap to serve as an alpha channel for the final image (Step 3) created on the fly.
Can anyone suggest how I might accomplish a similar functionality in Fabric? It doesn't seem to support HTML5 Canvas Blend modes and while I see that it supports clipping paths for images, I'm not seeing how I can enable the user to interactively create a clipping path without doing lots of intersection checks to try to derive the points to create a new path on the fly.
Thanks!
Step 1: After the user has drawn a base rectangle, drag-option/alt-key enables them to draw a rectangle (the red line) which will be subtracted from the base rect.
Step 2: The base rect is shown with the subtraction.
Step 3: The base rect is used to clip or mask a section of the base image
Step 1
Step 2
Step 3
Will Tower,
There is no easy way to do it. Here are the steps:
Draw 'Yellow' rectangle
Draw 'Red' rectangle
Use clipping library like PolyBool for intersection and xor operations
Convert drawing result into the clipped path of combining rectangles
clip your image
I created some quick fiddle. You have to click on a each button to clip. It won't clip if you will not add 2 rectangles on the canvas. This is very simple example. In order to work properly you have to draw rectangles with mouse (make them dynamic). Also, this logic is not accounting for these variations (you have to work on them as well):
For these use cases Clipping Library will return to you 2 set of results, which means different logic should be implemented.
Actual code without jQuery, FabriJs, and PolyBool libraries:
var imgURL = 'http://fabricjs.com/lib/pug.jpg';
var clipYellowRect = null;
var clipRedRect = null;
var pug = null;
var canvas = new fabric.Canvas('c');
// insert image into canvas
var pugImg = new Image();
pugImg.onload = function (img) {
pug = new fabric.Image(pugImg, {
angle: 0,
width: 500,
height: 500,
left: 100,
top: 50,
scaleX: 0.5,
scaleY: 0.5,
clipName: 'pug',
});
canvas.add(pug);
};
pugImg.src = imgURL;
//draw yellow rectangle
$('#btnYellowRect').on('click', function(){
clipYellowRect = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 120,
top: 60,
width: 200,
height: 200,
fill: 'rgba(255,255,0,0.5)',
strokeWidth: 0,
selectable: false
});
canvas.add(clipYellowRect);
});
//draw red rectangle
$('#btnRedRect').on('click', function(){
clipRedRect = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 90,
top: 120,
width: 100,
height: 100,
strokeWidth: 3,
fill: 'transparent',
stroke: 'rgba(255,0,0,1)', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
canvas.add(clipRedRect);
});
//clip
$('#btnClip').on('click', function(){
var yellowRectRegion = getRegion(clipYellowRect);
var redRectRegion = getRegion(clipRedRect);
//determine inersection
var intersectResult = PolyBool.intersect({
regions: [yellowRectRegion],
inverted: false
}, {
regions: [redRectRegion],
inverted: false
});
//generate clipping path
var xorResult = PolyBool.xor({
regions: [yellowRectRegion],
inverted: false
}, {
regions: intersectResult.regions,
inverted: false
});
clipImage(xorResult.regions[0]);
});
//prepare data for clipping library
function getRegion(rect){
return [[rect.left, rect.top],
[rect.left + rect.width, rect.top],
[rect.left + rect.width, rect.top + rect.height],
[rect.left, rect.top + rect.height]]
}
function clipImage(points){
//actual clipping
pug.clipTo = function (ctx) {
var scaleXTo1 = (1 / pug.scaleX);
var scaleYTo1 = (1 / pug.scaleY);
ctx.save();
var ctxLeft = -( pug.width / 2 );
var ctxTop = -( pug.height / 2 );
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
console.log(points)
ctx.moveTo(points[0][0] - pug.oCoords.tl.x, points[0][1] - pug.oCoords.tl.y);
for (var i=1; i < points.length; i++){
ctx.lineTo(points[i][0] - pug.oCoords.tl.x, points[i][1] - pug.oCoords.tl.y);
}
ctx.closePath();
ctx.restore();
};
clipYellowRect.remove();
clipRedRect.remove();
canvas.renderAll();
}
Hopefully it will help you.
Being new to Javascript, my understanding of this is a bit shaky. I've read a few articles and SO posts and learned quite a bit, but I'm having issues implementing some code. I'm using the KineticJS library to implement some canvas functions. I was working with a fiddle and was able to get an image to resize with the mouse control, however, when I put the code into a prototype function, resizing no longer works.
Canvas.prototype.addImg = function(){
var self = this;
var image;
var imageObj = new Image();
imageObj.onload = function() {
image = new Kinetic.Image({
x: 200,
y: 50,
image: imageObj,
width: 106,
height: 118,
draggable: false
});
self.backgroundLayer.add(image);
self.stage.add(self.backgroundLayer);
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg';
var circle1 = new Kinetic.Circle({
x: 150,
y: 150,
radius: 10,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: false
});
circle1.isResizing = false;
circle1.on("click", function (e) {
// toggle resizing true/false
var isResizing = !this.isResizing;
this.setDraggable(isResizing);
self.backgroundLayer.setDraggable(!isResizing);
this.setFill((isResizing ? "green" : "red"));
this.isResizing = isResizing;
self.backgroundLayer.draw();
});
circle1.on("dragmove", function () {
if (this.isResizing) {
var pos = this.getPosition();
var x = pos.x;
var y = pos.y;
var rectX = image.getX();
var rectY = image.getY();
image.setSize(x - rectX, y - rectY);
self.backgroundLayer.draw();
}
});
self.backgroundLayer.add(circle1);
self.backgroundLayer.draw();
}
I am assuming the problem lies within the use of this in the function, but I'm not sure how to fix the problem. Could somebody tell me what the problem is and how I would fix this?
I can see a problem with the line that is setting the size of image:
image.setSize(x - rectX, y - rectY);
For example if x is 100 and rectX is 200, you end up with a width of '-100', which is not legal.
In the fiddle you provided, when I use constant values, the image re-sizes perfectly:
rect.setSize(200, 200);
Modified fiddle.
The solution I've come upon, is that it is not an issue of this, but the setSize() method. For some reason or another, it is not working in my local application. When I replace this for setWidth() and setHeight(), everything works the way that it should.
I'm having a lot of trouble getting a smooth animation using Kinetic.js in Firefox. It looks great in Chrome and Safari, and even looks more-or-less okay in IE9, but Firefox is jerky. I tried using both the built-in Kinetic.Animate, and requestAnimationFrame, and both ended up looking the same. Any ideas?
<div id="container"></div>
<script>
$(function() {
var stage = new Kinetic.Stage({
container: 'container',
width: 1000,
height: 1000
});
var layer = new Kinetic.Layer();
var blackRect = new Kinetic.Rect({
x: 700,
y: 650,
width: 300,
height: 620,
fill: "black",
offset: [150, 620]
});
var colorRect = new Kinetic.Rect({
x: 300,
y: 650,
width: 300,
height: 620,
fill: "blue",
offset: [150, 620]
});
layer.add(blackRect);
layer.add(colorRect);
stage.add(layer);
function oscillate(node, time) {
var period = 5400;
var phase = 1200;
var amplitude = 1.2;
var shift = amplitude * Math.cos(phase + time * 2 * Math.PI / period);
node.setPosition(node.getX() + shift, node.getY());
}
function rotate(node, time) {
var period = 5400;
var amplitude = 0.08;
node.setRotation((amplitude * Math.sin(time * 2 * Math.PI / period) ));
}
function render(time) {
layer.draw();
}
var anim = new Kinetic.Animation(function (frame) {
oscillate(blackRect, frame.time);
oscillate(colorRect, frame.time);
rotate(blackRect, frame.time);
rotate(colorRect, frame.time);
}, layer);
anim.start();
});
</script>
Edit:
Here is the above example: http://jsfiddle.net/cantino/yr8Zr/
Yes, FF currently produces less-smooth animations.
You can get smoother-but-slower animation results by:
Using frame.timeDiff to throttle the frames-per-second,
And reducing your amplitude.
If smoother-but-slower breaks your design you can use the custom Kinetic.Shape to get "closer to the metal".
With Kinetic.Shape, you get a canvas context to draw on instead of relying on the easier (but less performant) Kinetic.Rect.
I know that this question was answered long time ago, but i encountered this issue recently too, and the answers provided here gave only a marginal performance boost.
So i looked into the core of KineticJS and found a fix that got my FPS from 10 to almost 60. Some times the FPS dropped as low as 2.
The fix is for those who may encounter this issue in the future and search for an answer.
stage._mousemove = Kinetic.Util._throttle( stage._mousemove, 60);
someKineticLayer._getIntersection = function() {return {};};
//keep in mind that tampering with _getIntersection will disable mouse interaction for that layer and may have other effects.
You can interact with this code here on jsFiddle
In the fiddle you can see that I have made a flag (Kinetic.Rect) on a flagpole (Kinetic.Line). I desire to fire an event when the user moves the mouse over any portion of the flag or flagpole. In prior attempts I have attached event handlers to each shape individually, only to learn that Kinetic.Line does not fire events.
In this latest attempt I added the shapes to a group and attached the handler to the group thinking this would solve the issue: it does not.
Is there any way to achieve the desired behavior? Thank you, and remember to press F12 to see the handler's console messages.
var handler = function(e) {
console.log("Event fired.");
};
var stage = new Kinetic.Stage({
container: 'testBlock',
width: 200,
height: 200
});
var layer = new Kinetic.Layer();
var group = new Kinetic.Group();
var rect = new Kinetic.Rect({
x: 75,
y: 10,
width: 50,
height: 50,
fill: 'silver',
});
line = new Kinetic.Line({
points: [
{x: 125, y: 10},
{x: 125, y: 160},
],
stroke: 'black',
strokeWidth: 1
});
// add the shapes to the group
group.add(rect);
group.add(line);
// event handler for the group
group.on("mouseover", handler);
// add the group to the layer
layer.add(group);
// add the layer to the stage
stage.add(layer);
Kinetic.Line's have trouble with events when the stroke is too small, you can see this evident with any line with stroke < 3px.
This was the response I got from Eric Rowell (creator of KineticJS):
yep, KineticJS ignores the anti-aliased pixels. If you're drawing a 1px diagonal line, and you want it to be detectable, you need to create a custom hit function to define the hit region. You probably will want to create a hit region that's a line which is about 5px thick or so. Here's an example on creating custom hit regions:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-custom-hit-function-tutorial/
So in addition to Ani's answer, you can also use the drawHitFunc property to make a custom hit region for the line that is thicker than the actual line:
line = new Kinetic.Line({
points: [
{x: 125, y: 10},
{x: 125, y: 160},
],
stroke: 'black',
strokeWidth: 1,
drawHitFunc: function(canvas) {
var x1=this.getPoints()[0].x;
var x2=this.getPoints()[1].x;
var y1=this.getPoints()[0].y;
var y2=this.getPoints()[1].y;
var ctx = canvas.getContext();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(x1-3,y1-3);
ctx.lineTo(x1-3,y2+3);
ctx.lineTo(x2+3,y2+3);
ctx.lineTo(x2+3,y1-3);
ctx.closePath();
canvas.fillStroke(this);
}
});
jsfiddle
Try this fiddle
I'm using Kinetic.Rect with width=1 and height= y2-y1 of your line.
line = new Kinetic.Rect({
x: 125, y: 10,
width: 1, height: 150,
fill: 'black',
});
I'm trying to get kineticjs down and worked out a little app which makes my images draggable and resizable. So far so good;
However: I want an overlay with a variable height/width block in the center which should show the image underneath(With the draggable/resizable intact) with a semi-transparent overlay.
I want to be able to still resize/drag behind the overlay while the overlay is still intact(Like this, but with kineticjs: http://envyum.nl/pointer/)
Is there a way to do so? By cutting a block out of an overlaying rectangle perhaps? And can the mouse ignore the overlay such as pointer-events: none can in css3?
Thanks in advance,
I have a sample of what I was talking about in the comments above: http://jsfiddle.net/KwQBB/
This did not require a new layer, but might be good practice to do so.
You can tailor the logic to be whatever you want, especially to simulate a 'cut-out'
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 500
});
var layer = new Kinetic.Layer();
var pentagon = new Kinetic.RegularPolygon({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
sides: 5,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true,
dragOnTop: false
});
var rect1 = new Kinetic.Rect({ // overlay
x: 0,
y: 0,
width: stage.getWidth(),
height: 100,
fill: 'gray',
opacity: 0.5,
listening: false // <------ Extremely important
});
var rect2 = new Kinetic.Rect({ // overlay
x: 0,
y: stage.getHeight()/2,
width: stage.getWidth(),
height: 100,
fill: 'gray',
opacity: 0.5,
listening: false // <------ Extremely important
});
// add the shape to the layer
layer.add(pentagon);
layer.add(rect1);
layer.add(rect2); // add more rectangles to complete overlay
// add the layer to the stage
stage.add(layer);