Archive

Archive for the ‘javascript’ Category

requestAnimationFrame for smart animating

February 22nd, 2011

If you've never written code to animate inside the browser, you can stop reading :)

What is requestAnimationFrame?

In your animation work, you've used a timer loop to make changes every few milliseconds. Good for us: browser vendors have decided, "hey, why don't we just give you an API for that, because we can probably optimize some things for you." So it's basic API for use with animation, whether that be DOM-based styling changes, canvas or WebGL.

Why should I use it?

The browser can optimize concurrent animations together into a single reflow and repaint cycle, leading to higher fidelity animation. For example, JS-based animations synchronized with CSS transitions or SVG SMIL. Plus, if you're running the animation loop in a tab that's not visible, the browser won't keep it running, which means less CPU, GPU, and memory usage, leading to much longer battery life.

OMG I can brag about having a site with battery-friendly animations?

Yeah bro. Totes McGoats.

How should I use this?

 
    // shim layer with setTimeout fallback
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function( callback ){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
 
 
    // usage: 
    // instead of setInterval(render, 16) ....
 
    (function animloop(){
      requestAnimFrame(animloop);
      render();
    })();
    // place the rAF *before* the render() to assure as close to 
    // 60fps with the setTimeout fallback.

Note: I am using 'requestAnimFrame` here because since the spec is still in flux, I don't want to make a straight polyfill, yet.

2012.01.07: The spec has gotten some fixes and settled down. We're also seeing ms and o implementations, so we're ready for a polyfill now!

A robust polyfill

Opera engineer Erik Möller wrote about rAF and developed a polyfill that better handles browsers without native support. You can read about it, but basically his code will choose a delay of between 4ms and 16ms in order to more closely match 60fps. Here it is, in case you'd like to use it. Note it uses the standard method name. I have also fixed the cancel* method's name, as it has changed in WebKit.

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = 
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

I have this polyfill available as a gist as well.

Let's see that in action

The requestAnimationFrame API

window.requestAnimationFrame(function(/* time */ time){
	// time ~= +new Date // the unix time
});

The callback is passed the current time as the first argument, as you'll likely want that.

You'll also notice the second parameter to requestAnimFrame: the element that visually bounds the entire animation. For canvas and WebGL, this will be the actual <canvas> element. For DOM stuff, you can leave it off or define it for a slightly more optimized experience.It's since been removed from the spec (and WebKit's implementation)

Is it ready?

Right now, the Webkit implementation (available in Nightly Safari and Chrome Dev Channel) and the Mozilla one (available in FF4) differ slightly. Mozilla's has a bug which caps potential framerates at about 30fps.Actually, "It's currently capped at 1000/(16 + N) fps, where N is the number of ms it takes your callback to execute. If your callback takes 1000ms to execute, then it's capped at under 1fps. If your callback takes 1ms to execute, you get about 60fps." (thx, Boris) It'll be fixed though, probably for the next release after FF4 ships. Also Chrome 10 doesn't have the time parameter (added in m11), FF currently ignores the element argument.

We want your feedback!

If you do animation work, WebKit and Gecko hackers would love to hear if this meets your needs or if it could be improved any. Take a look at the draft requestAnimationFrame spec. It's currently modeled like setTimeout; does a setInterval repeating style API make more sense? Does this API have downsides when animating multiple simultaneous elements? Does the element reference work for you? In general, does this solve the common cases of animation?

Resources for hackers

Thx @mrdoob for bringing this up today and spreading the good word.

2011.06.01: Joe Lambert took this idea and provided shims for both setTimeout and setInterval that leverage this. http://blog.joelambert.co.uk/2011/06/01/a-better-settimeoutsetinterval/

2011.10.13: Updated code sample and explained why the rAF call before render makes sense due to the timeout fallback.

2011.12.14: Updated code sample and text. Removed references to element argument, as it was removed from spec and webkit's impl.

2012.01.01: Added polyfill from Erik Moller.
2012.01.05: Addressed polyfill fix from Tino Zijdel of DHTML Lemmings fame. :)

Paul Irish javascript

Surefire DOM Element insertion

February 16th, 2011

So you've got an element. You need to add it to the DOM. Somewhere, anywhere, just be sure that it works.

In Modernizr, we need to do this all the time. In order to adequately do our feature detection we need to try out some elements in the DOM. But how exactly to add it? Steve Souders dug into appendChild vs insertBefore and the comments meandered through the sea of possibilities. Here's the best way to add yourElem:

var ref = document.getElementsByTagName('script')[0];
ref.parentNode.insertBefore(yourElem, ref);

But you prefer a good old document.body.appendChild(elem); Awww! I know, man. I used to, too.
Well, you can't always trust this.. Especially if you're writing library, widget or third-party code.

Here's why you can trust this insertBefore method, and why all the other techniques are not wise:

  • appendChild can throw Operation Aborted
  • <head> doesn't always exist in old opera, not-so-old safari, and others
  • if your script runs in the <head>, the <body> doesnt exist yet.
  • appending to document.documentElement can lead to an uncertain parentNode.
  • document.documentElement.firstChild (or .childNodes[0]) may be an html comment.
    • and as of FF4, insertBefore doesnt work with an html comment node

Okay okay okay, there is actually one case where the above method poses a problem:

  1. You are inserting a <link>, <script>, or <img> (sumpthin' referencing another asset).
  2. Said asset has a relative URL. (Yours has an absolute URL? You're A-OK, bud)
  3. The page has a <base> element, changing the base URL used to resolve all relative URLs used.
  4. The first <script> element of the page is after said <base> element.
  5. You don't want your element's asset path to be affected by the <base> element.

Wow. So if this scenario is a concern for you.. Then you need a small addition to the earlier code:

var ref = document.getElementsByTagName('base')[0] || document.getElementsByTagName('script')[0];
ref.parentNode.insertBefore(yourElem, ref);

But anyway that's a pretty narrow edge case so most people will be fine with the earlier snippet.

And if you have a hard time remembering the signature for insertBefore like I do, just think that your parent wants you to insert yourself before your younger brother. That's totally parentNode.insertBefore(you, sibling), right? :)

Thx souders, alex sexton, miketaylr, ben alman, jdalton & everyone else who cares about these small details

Paul Irish javascript

11 More Things I Learned from the jQuery Source

January 19th, 2011

I recently dove back into the jQuery source code to pull out some more JavaScript goodness. If you haven't seen the original 10 Things I Learned from the jQuery Source video, definitely check that out.

But now.. some newness:


youtube link for flash-less HTML5 player

In this video:

Tune in for 29 minutes and soak it up.

Paul Irish javascript, jquery

My harmonious background canvas

May 17th, 2010

So I happened upon squaredesign site and was impressed by their trippy background squares that responded to mouse movement (tutorial here).


Illustration done with Harmony

I got jealous and wanted my own interactive background. Plus @miketaylr's is mad sweet. Turns out, I'm lazy. Never fear. Mr Doob recently put out the magical canvas procedural illustration application Harmony and generously licensed it MIT.

I adapted the code for use as a background, but felt like it still missed something. It needed an easter egg. Oh yes. Taking inspiration from the Harmony Harmony meme (example), I decided an Erasure music video was in order.

Leveraging code from the $1 Unistroke Recognizer (which is righteous), it takes snapshots between drawing pauses and looks to see if you drew a star. If it thinks you did.. then:

The code for the bg integration is on github: /paulirish/harmony
The code for doob's harmony app is on github, too: /mrdoob/harmony

Take it for a spin if you like. Experiment with some of the other brushes. Thanks again to powerhouse Mr Doob for sharing such beautiful work.

(I'll probably be removing the drawing canvas in a week or so. It's a bit of a resource hog!)

Paul Irish javascript

Updates from all around – Dec 2009

December 19th, 2009

A lot of the work I'm doing doesn't turn into new posts so here's a summary of what's been going on:

yayQuery

Since it was announced here, we've come a long way. We just put out our 7th episode (now in HD!). The show format has tightened up and we're putting a lot of time into making it even better. Follow us on twitter or subscribe to catch all the goodness. We also put out an entire episode devoted to what's coming in jQuery 1.4 (aka 1.MOAR), so peep it if you're curious.

jQuery Singalong plugin

I was a finalist at Boston's Music Hack Day with the jQuery Singalong plugin. It essentially allows you to add timed annotations to the HTML5 audio and video elements. (Also it was the first time I legitimately used javascript's Infinity :) In addition, I put out the (very!) alpha jQuery Bouncing Ball plugin. Check out the demos to get a taste

Modernizr updates

Faruk and I pushed out Modernizr 1.1, which can now detect what audio/video formats your browser supports, all sorts of HTML5 forms goodness, and some other ones like localStorage and applicationCache. We're delighted to be part of Mark Pilgrim's Dive into HTML5 book and pumped to help push the edge of progressive enhancement.

@font-face and webfonts

It's a rapidly changing landscape, so I've been keeping all my font-y posts up to date:

@font-face feature detection

I've updated the existing feature detection script to use a smaller file. (Thanks to the Font Squirrel for the help). You would use this script to detect if support is present or not; perhaps implement a Cufon fallback. These new improvements will make their way into Modernizr 1.2.

I've also added on a isFontFaceSupported browser sniffing alternative. The main benefit here is that you're guaranteed accurate results synchronously. (The true feature detection can't do that, though the new small size is much faster in Firefox.)

Other bits and bobs

The nice guys at that other jQuery Podcast had me on the show in November. I talked about, well.. basically all of the stuff above. :) It's a good show.

I tweeted about a new quick approach to a for loop:

for (var i = -1, len = arr.length; ++i < len; ) // the cool guy loop

The fun part is that the counting expression (the third part) is empty. It's faster than your standard loops, but reverse while() is still quicker. @jdalton beat me to this one, for the record.

Lastly, along with my writers, I've been keeping my mp3 blog Aurgasm fresh with new music you'll love. I've got a lot more things in the pipeline, so right after my Christmas and New Years jaunt around Germany, Denmark, and Scotland, you'll see them soon. Happy December ya'll.

Paul Irish javascript, jquery, typography

i left this space here for you to play. <3