2
votes

I'm trying to learn how to create smooth JavaScript animations using HTML5's canvas. For some reason, the animation is not smooth, but kind of "sputtery".

You can see the framework I've built on this jsFiddle, which only uses Webkit properties at the moment.

Another developer was able to create the same concept using their WebViews source code, which is based on Ext.js. For learning purposes, I wanted to avoid using any libraries in effort to better understand JavaScript. The WebViews concept can be viewed at this jsFiddle, which shows a much smoother animation.

I've read and tried all kinds of different scenarios from pulling the update calls out of the requestAnimationFrame into it's own loop, to translating the context to the draw position, to drawing to a back-buffer context and copying it to the viewport. The following code represents my best efforts so far.

Any suggestions on how to improve the performance in order to get an object to move smoothly without all the overhead of an external library?

Thank you in advance.

var app;
var now = then = delta = 0;
var viewport = document.createElement( 'canvas' );
var viewportContext = viewport.getContext( '2d' );

function App( )
{
    this.circle = {
        position : viewport.width / 2,
        radius : 10
    };
}

App.prototype.initialize = function( )
{
    app = this;
    document.body.appendChild( viewport );
    viewport.width = 320;
    viewport.height = 200;

    webkitRequestAnimationFrame( app.render, viewport );
};

App.prototype.render = function( )
{
    now = performance.webkitNow( );
    delta = ( now - then ) / 1000.0;
    then = now;

    app.update( delta );
    viewportContext.clearRect( 0, 0, viewport.width, viewport.height );
    app.draw( viewportContext );

    webkitRequestAnimationFrame( app.render, viewport );
};

App.prototype.draw = function( context )
{
    context.fillStyle = "white";
    context.beginPath( );
    context.arc( this.circle.position | 0, viewport.height / 2 | 0, this.circle.radius | 0, 0, Math.PI * 2, true );
    context.closePath( );
    context.fill( );
};

App.prototype.update = function( deltaTime )
{
    this.circle.position += viewport.width / 5 * deltaTime;

    if( this.circle.position >= viewport.width )
    {
        this.circle.position = 0;
    }
};

window.onload = function( )
{
    new App( ).initialize( );
};​
2
Running smooth for me. That being said, you can profile your app by visiting your F12 developer tools and checking out the profiling options - this will show you which parts of your app are eating up the most resources and time. - Sampson
I don't see a significant difference in the smoothness of your demo compared to your WebViews demo. I am running Chrome 21.0.1180.89 m. - Seth Flowers
Hmmm...any difference is a difference, and for my demo it is not very noticeable, but it is there. I'm running the same version of Chrome, and I have tried making sense of Heap Profiles and Memory Timeline in the developer tools, to no avail. The fact that I can't achieve the same level of fidelity as the WebViews demo is driving me crazy. - Jeff Jenkins
Also, this page has a ton of information on the subject. - Shmiddty
Notice here the improved smoothness: jsfiddle.net/3TAVu/1 Doesn't make sense, but I believe it has to do with the size of the canvas. - Shmiddty

2 Answers

4
votes

See this page for many common optimizations, and great explanations about why and how they improve performance.

Also, for some reason, performance seems to increase on a more "medium-size" canvas. I'm not entirely sure why this is, but I believe it has to do with browser optimization.

You can note some performance gains with a few small tweaks here: http://jsfiddle.net/3TAVu/1/

Specifically, I removed the superfluous assignment to fillStyle here:

App.prototype.draw = function( context )
{
    context.beginPath( );
    context.arc( this.circle.position | 0, viewport.height / 2 | 0, this.circle.radius | 0, 0, Math.PI * 2, true );
    context.closePath( );
    context.fill( );
};

I also modified the render method by clearing only the relevant portion of the canvas instead of the entire thing:

App.prototype.render = function( )
{
    now = performance.webkitNow( );
    delta = ( now - then ) / 1000.0;
    then = now;

    var cX = app.circle ? (app.circle.position - app.circle.radius) : 0;
    var cY = Math.round(viewport.height/2) - app.circle.radius;
    var w = app.circle ? app.circle.radius * 2 : 0;

    viewportContext.clearRect(cX - 1, cY - 1, w + 2, w + 2);

    app.update( delta );
    app.draw( viewportContext );

    webkitRequestAnimationFrame( app.render, viewport );
};
3
votes

hey I've found this very well explained page about requestAnimationFrame thing, which deals with smoother animations. In case you or future viewer of this question may find this useful..