• No results found

All modern browsers support these features and have for the last two versions

In document HTML5 Programming Jonathan Reid (Page 163-171)

W3C Candidate Recommendation:

http://www.w3.org/TR/animation-timing/

The Animation Timing standard is designed to help you build JavaScript-based visual animations.

If you have ever tried to build an animation by hand using JavaScript, you’re probably familiar with the simple pattern of a draw cycle:

• Create a draw function that is responsible for incrementally “drawing” the animated items: positioning elements, changing element properties, drawing on a canvas element, and so forth. Each time this function is called, it produces an entire animation “frame,” just as if you were drawing animation frames by hand that would then be shown in a film.

• Call the draw function every few milliseconds.

A JavaScript draw cycle is typically implemented using a timer, which calls a drawing function every few milliseconds. An example can be seen in Listing 5-3.

Listing 5-3. A JavaScript Implementation of a Timer-Based Draw Cycle

<!DOCTYPE html>

<html>

<head>

<title>The HTML5 Programmer's Reference</title>

<style>

#target-element { width: 100px;

height: 100px;

background-color: #ccc;

position: absolute;

top: 100px;

left: 0px;

}

</style>

</head>

<body>

<h1>Simple Animation Example</h1>

<div id="target-element"></div>

<script>

// Get a reference to the element we want to move.

var targetEl = document.getElementById('target-element');

// Create a variable to keep track of its position.

var currentPosition = 0;

/**

* Draws the animation by updating the position on the target element and incrementing * the position variable by 1.

*/

function draw() {

if (currentPosition > 500) {

// Stop the animation, otherwise it would run indefinitely.

clearInterval(animInterval);

} else {

// Update the element's position.

targetEl.style.left = currentPosition++ + 'px';

} }

// Initiate the animation timer.

var animInterval = setInterval(draw, 17);

</script>

</body>

</html>

This example uses a JavaScript timer to update the position of a div on the page. The interval between updates is 17 milliseconds. That’s not an arbitrary number. Most monitors refresh at 60Hz, and so most browsers try and limit their screen repaints to no more than 60Hz. Sixty cycles per second is about 17 milliseconds between cycles. Any faster than that and you lose “frames.”

Depending on the browser you use to run this example, and the system you are using, this animation can appear to be quite smooth or somewhat jerky. That’s because this is a brute-force method of animation, and it doesn’t take into account how the browser redraws the page. It just commands the screen to be updated, and the browser has to do the best it can. Also, there’s no guarantee that the time between animation updates will be 17 milliseconds. The setInterval method just adds the updates to the browser’s UI queue, which can easily become bogged down if the browser is busy doing something else (like resizing the window, or possibly fetching and rendering other content in the background), thus delaying the screen render.

Overall this method doesn’t scale well. As animations increase in number and complexity, and the pages they are in also increase in complexity and interactive capability, these timer-based animation queues become more and more inefficient.

The Animation Timing specification addresses the problems with JavaScript-based timers by providing a new timer: requestAnimationFrame. Syntactically this method is used similarly to the existing JavaScript timer methods setInterval and setTimeout. Behind the scenes, though, the new method is tied to the browser’s screen management algorithms. As a result, requestAnimationFrame has some important benefits:

• Animations queued with requestAnimationFrame are optimized by the browser into a single reflow/repaint cycle.

• Animations queued with requestAnimationFrame play well with animations from other sources, like CSS transitions.

• The browser will stop animations in browser tabs that are not visible. This is important on mobile devices, where intensive animations can rapidly consume battery power.

The specification creates two new methods in the global context:

• requestAnimationFrame(callback): Request that the function callback be executed as part of the next animation cycle. The callback will receive as a parameter a timestamp. Like setTimeout and setInterval, requestAnimationFrame returns an identifier that can be used to stop the cycle.

• cancelAnimationFrame(identifier): cancel the animation frame request identified by the identifier.

Updating Listing 5-3 to use requestAnimationFrame is easy, as shown in Listing 5-4.

Listing 5-4. Listing 5-3 Rewritten Using requestAnimationFrame

<!DOCTYPE html>

<html>

<head>

<title>The HTML5 Programmer's Reference</title>

<style>

<h1>Simple requestAnimationFrame Example</h1>

<div id="target-element"></div>

<script>

var targetEl = document.getElementById('target-element');

var currentPosition = 0;

/**

* Updates the position on the target element, the increments the position * counter by 1.

*/

function animateElement() {

// Stop the animation, otherwise it would run indefinitely.

if (currentPosition <= 500) {

requestAnimationFrame(animateElement);

}

// Update the element's position.

targetEl.style.left = currentPosition++ + 'px';

}

// Initiate the animation timer.

animateElement();

</script>

</body>

</html>

This example updates the animateElement function to use requestAnimationFrame. Each time that method is called, it updates the position of the element and increments the position counter. It also schedules itself for calling again via requestAnimationFrame. Once the element reaches the position of 500px, the animation stops.

Building a draw cycle manager using Animation Timing is also quite easy. A draw cycle manager will allow you to register animation functions (like the animateElement function in Listing 5-4), and start, stop, and pause the draw cycle. Listing 5-5 shows a simple draw cycle manager.

Listing 5-5. A Draw Cycle Manager

<!DOCTYPE html>

<html>

<head>

<title>The HTML5 Programmer's Reference</title>

<style>

<h1>Simple Animation Framework Example</h1>

<div class="animatable" id="elementOne"></div>

<div class="animatable" id="elementTwo"></div>

<button id="startAnimation">Start Animation</button>

<button id="togglePause">Toggle Pause</button>

<button id="stopAnimation">Stop Animation</button>

<button id="registerOne">Register Animation One</button>

<button id="unregisterOne">Unregister Animation One</button>

<button id="registerTwo">Register Animation Two</button>

<button id="unregisterTwo">Unregister Animation Two</button>

<script>

// Get references to the elements we will be animating, and create position // tracking variables for them.

var elementOne = document.getElementById('elementOne');

var elOnePosition = 0;

var elementTwo = document.getElementById('elementTwo');

var elTwoPosition = 0;

/**

* Animates Element One by incrementally updating its left position. Animation * stops at 500px.

*/

function animateElementOne() { if (elOnePosition <= 500) {

elementOne.style.left = elOnePosition++ + 'px';

} else {

// Done animating, so remove this animation from the draw cycle manager.

myCycle.removeAnimation(animateElementOne);

// Reset the counter so we can animate again. The function can be

* Animates Element Two by incrementally updating its left position. Animation * stops at 500px.

*/

function animateElementTwo() { if (elTwoPosition <= 500) {

elementTwo.style.left = elTwoPosition++ + 'px';

} else {

// Done animating, so remove this animation from the draw cycle manager.

myCycle.removeAnimation(animateElementTwo);

// Reset the counter so we can animate again. The function can be

* Creates a draw cycle object that will repetitively draw animation functions.

* @constructor

* @returns {Object} A new draw cycle object.

*/

var DrawCycle = function() { var newCycle = {

/**

* The identifier for the current animation frame loop.

* @type {Number}

*/

animationPointer: null, /**

/**

* Starts the animation cycle.

*/

startAnimation: function() {

// Like other JavaScript timers, requestAnimationFrame sets the execution // context of its callbacks to the global execution context (the window // object). We need the execution context to be 'this', the newCycle // object we're creating. By using the bind method (which exists on // Function.prototype) we are able to override the default execution // context with the one we need.

this.animationPointer = window.requestAnimationFrame(this.draw.bind(this));

}, /**

* Stops the animation cycle.

*/

stopAnimation: function() {

window.cancelAnimationFrame(this.animationPointer);

}, /**

* Pauses the invocation of the animation functions each draw cycle. If set * to true, the animation functions will not be invoked. If set to false, * the functions will be invoked.

* @type {Boolean}

*/

pauseAnimation: function(boolPause) { this.isPaused = boolPause;

addAnimation: function(callback) {

if (this.arrCallbacks.indexOf(callback) == -1) { this.arrCallbacks.push(callback);

} }, /**

* Removes an animation function from the draw cycle.

* @param {Function}

*/

removeAnimation: function(callback) {

var targetIndex = this.arrCallbacks.indexOf(callback);

if (targetIndex > -1) {

this.arrCallbacks.splice(targetIndex, 1);

} },

/**

* Draws any registered animation functions (assuming they are not paused) * and then kicks off another animation cycle.

var i = 0, arrCallbacksLength = this.arrCallbacks.length;

for (i = 0; i < arrCallbacksLength; i++) {

// Create a new draw cycle object.

var myCycle = new DrawCycle();

// Register a callback for the Start Animation button that starts the animation // cycle.

var startAnimation = document.getElementById('startAnimation');

startAnimation.addEventListener('click', function() { myCycle.startAnimation();

}, false);

// Register a callback for the Pause Animation button that pauses/unpauses the // animation cycle.

var pauseAnimation = document.getElementById('togglePause');

pauseAnimation.addEventListener('click', function() { myCycle.pauseAnimation(!myCycle.isPaused);

}, false);

// Register a callback for the Stop Animation button that stops the animation // cycle.

var stopAnimation = document.getElementById('stopAnimation');

stopAnimation.addEventListener('click', function() { myCycle.stopAnimation();

}, false);

// Register a callback for the Register Animation One button that adds the // animation function for element one to the draw cycle object.

var registerOne = document.getElementById('registerOne');

registerOne.addEventListener('click', function() { myCycle.addAnimation(animateElementOne);

}, false);

// Register a callback for the Unregister Animation One button that removes the // animation function for element one from the draw cycle object.

var unregisterOne = document.getElementById('unregisterOne');

unregisterOne.addEventListener('click', function() { myCycle.removeAnimation(animateElementOne);

}, false);

// Register a callback for the Register Animation Two button that adds the // animation function for element two to the draw cycle object.

var registerTwo = document.getElementById('registerTwo');

registerTwo.addEventListener('click', function() { myCycle.addAnimation(animateElementTwo);

}, false);

// Register a callback for the Unregister Animation Two button that removes the // animation function for element two from the draw cycle object.

var unregisterTwo = document.getElementById('unregisterTwo');

This example creates a constructor function that gives you a new draw cycle object, which provides a simplified API for handling animations. The main API methods are:

• addAnimation(animationFunction): Registers an animation function with the draw cycle. Every time the draw cycle runs, animationFunction will be invoked.

• removeAnimation(animationFunction): Deregisters an animation function with the draw cycle.

• startAnimation(): Starts the animation drawing cycle. When called, this method will call requestAnimationFrame with the object’s draw method as the callback, thus initiating a single loop. The method stores the identifier for the loop so that it can later be cancelled if desired.

• stopAnimation(): Stops the animation drawing cycle. When called, this method calls cancelAnimationFrame with the identifier stored by startAnimation.

• pauseAnimation(boolPause): Pauses or unpauses calling the registered animation functions. The draw cycle still runs but none of the animation functions are invoked.

Using this animation API is simple:

1. Create a new instance of a draw cycle using the constructor function.

2. Register one or more animation callbacks that you want to be called every draw cycle.

3. Start the animation cycle.

When you call startAnimation it requests an animation frame from the browser, with draw method as the callback. The browser invokes the draw method at the appropriate time. The draw method invokes all of the registered animation functions (assuming animation is not paused), thus completing one cycle. It then calls startAnimation to kick off a new cycle.

You can dynamically add new animation functions as desired; they will automatically be invoked in the next draw cycle. You can also remove animation functions as needed. Each animation method also removes itself from the draw cycle when it completes, and resets its counter. You can reregister the animation functions at that point and the animations will happen again. Note that the draw cycle will continue to run even if there are no animation functions registered, so when you remove the last animation function you also should be sure to call the stopAnimation method.

Selectors

SUppOrt LeVeL

excellent

In document HTML5 Programming Jonathan Reid (Page 163-171)