Transformations and Our Car Animation
Transformations and Our Car Animation
Getting back to our car animation, we’re sure you can think of a way that we might be able to use rotation to make our animation more believable again. That’s right, let’s make those wheels turn. Obviously, we will require a separate wheel image to be able to apply the rotation to it, and not the rest of the car. Luckily, we have one.
Create a new HTML file for this demo, called wheelie.html, and a corresponding wheelie.js file for the code:
(function() { var ANIMATION_DURATION = 3000;
var canvas = null, context = null, car = null, wheel = null, endPos = null, endAngle = 0, wheelOffset = 0, animationStart = 0;
function resetCanvas() { ... } // resetContext
function drawWheel(x, y, rotation) { if (wheel && wheel.complete) {
context.save(); try {
// translate and rotate around the wheel center
context.translate(x, y); context.rotate(rotation);
// draw the wheel image (taking into account the wheel image size)
context.drawImage(wheel, -wheelOffset, -wheelOffset);
} finally {
context.restore(); } // try..finally
} // if } // drawWheel
function animate() { context.save(); try {
if (endPos && car && car.complete) { // determine the elapsed time var elapsedTime = new Date().getTime() - animationStart,
carX = PROWEBAPPS.Easing.Back.Out( elapsedTime, 0, endPos.x, ANIMATION_DURATION) - car.width,
wheelAngle = PROWEBAPPS.Easing.Back.Out(
CHAPTER 7: Exploring Interactivity
elapsedTime, 0, endAngle, ANIMATION_DURATION);
// clear the drawing surface context.clearRect(0, 0, canvas.width, canvas.height);
// draw the car context.drawImage(car, carX, endPos.y - car.height);
// draw the wheels at the appropriate position
drawWheel(carX + 17, endPos.y - 10, wheelAngle); drawWheel(carX + 99, endPos.y - 10, wheelAngle);
// if the car x is greater than the end pos, then remove it if (elapsedTime > ANIMATION_DURATION) {
endPos = null; } // if } // if } finally {
context.restore(); } // try..finally } // animate
function startCar(destX, destY) { endPos = { x: destX, y: destY
// calculate the end angle based on the end x position
endAngle = (endPos.x / window.innerWidth) * 8 * Math.PI;
// capture the animation start tick count animationStart = new Date().getTime();
} // startCar
$(window).bind("resize", resetCanvas).bind("reorient", resetCanvas);
$(document).ready(function() { window.scrollTo(0, 1); resetCanvas();
document.body.addEventListener("touchstart", function(evt) { startCar( evt.touches[0].pageX / window.devicePixelRatio, evt.touches[0].pageY / window.devicePixelRatio);
// prevent screen scrolling evt.preventDefault();
}, false);
// load our car image car = new Image(); car.src = "car.png";
CHAPTER 7: Exploring Interactivity
wheel = new Image(); wheel.src = "wheel.png"; wheel.onload = function() {
wheelOffset = wheel.width * 0.5;
setInterval(animate, 20); }); })();
The result is shown in Figure 7–10. Walking through the functionality of this code, we can see the following significant details:
We add a drawWheel function that is responsible for rotating the canvas around a particular point and then drawing the wheel image so that it is centered on that point. We use the same technique that we used in the earlier rotation sample—we translate the origin of the canvas to the center point where the wheel was drawn, apply the rotation, and then call drawImage to draw the wheel image at the appropriate position.
Inside the animate function, we calculate the angle that we should rotate the wheel by. We do this by applying the same tween function that we are using to animate the x position of the car image. This means that the wheels move in sync with the car. The example uses the PROWEBAPPS.Easing.Back.Out, but, if you chose not to implement any additional easing equations, you can obtain the required source code from the GitHub repository, at http://github.com/sidelab/prowebapps- code/blob/master/js/prowebapps.js. Alternatively, feel free to use one of the easing equations implemented earlier if that is preferred.
The drawWheel function is called twice in the animate function—once for each wheel.
In startCar (which is essentially the functionality that used to be contained within the touchstart handler), a variable called endAngle is initialized. This variable is used in the wheel-easing calculation, and is set relative to the distance of the x position that we are sending the car to. By calculating this value relative to the end position of the car, the wheels move at a speed appropriate for the distance that the car has to move.
Finally, the wheel image is loaded after the car image. For the wheel image load, we attach an onload handler so the wheelOffset can be calculated for an accurate wheel-imaging position in the drawWheel function.
CHAPTER 7: Exploring Interactivity
Figure 7–10. Animating the wheels provides a convincing animation.
NOTE: As per previous notes regarding strange behavior in Android 2.1 and canvas drawing, the rotated wheel does not appear correctly for that version. Every other version of Android is fine. While we have shown you some techniques on how to combat the oddities of 2.1 in your application code, if it is possible, then it would be wise to recommend users use Android 2.2 or greater for any web applications that make use of the HTML5 canvas.
The adoption of Android 2.2 is accelerating, and since we initially wrote the contents of this chapter (at which time 2.1 was the dominant version) 2.1 now runs second in usage to 2.2 (as at January 4, 2011 Android 2.2 is installed on 51.8 percent of devices, and 2.1 is now at 35.2 percent). You can keep an eye on Android OS version distribution ratio at the following url: http://developer.android.com/resources/dashboard/platform-versions.html.
While we considered removing the content on the “tweaks” that were required to deal with the inadequacies of 2.1, we felt that content still offered value to those of you who might have to deal with 2.1 during your mobile web application development. If you are in that group, then our thoughts are with you—good luck.
CHAPTER 7: Exploring Interactivity