}, false);
function onMouseUp () { isMouseDown = false;
canvas.removeEventListener('mouseup', onMouseUp, false);
canvas.removeEventListener('mousemove', onMouseMove, false);
}
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
if (!isMouseDown) {
If you are calculating simple easing to a single target, eventually you’ll get to the point where the object is at the target and the purpose of the easing has been achieved. But, in the examples so far, the easing code continues to execute, even though the object isn’t visibly moving anymore. If you are just easing to that point and leaving the object there, continuing to run the easing code is a waste of system resources. If you’ve reached your goal, you might as well stop trying. At first glance, this would be as simple as checking whether the object is at its target and turning off the animation loop, like so:
Download from Wow! eBook <www.wowebook.com>
175
if (ball.x === targetX && ball.y === targetY) {//code to stop the easing }
But it winds up being a little more tricky. I touched on this problem in earlier chapters when we applied friction, but we'll examine it in a little more detail here.
The type of easing we are discussing involves something from Zeno’s Paradoxes. Zeno of Elea was a Greek philosopher who devised a set of problems to show that, contrary to what our senses tell us, there is no change, and motion is an illusion. To demonstrate this, Zeno explained motion as follows: In order for something to move from point A to point B, it first must move to a point halfway between the two. Then it needs to travel from that point to a point halfway between there and point B. And then halfway again.
Since you always need to move halfway to the target, you can never actually reach the target, since it is an infinite amount of steps away.
It's a paradox because it sounds logical, but yet, our own experiences tell us that we move from point A to point B every day. Let’s take a look at it where the infinite breaks down in JavaScript. On the x axis, an object is at position 0. Say you want to move it to 100 on the x axis. Make the
easing
variable 0.5, so it always moves half the distance to the target. It progresses like this: Starting at 0, after frame 1, it will be at 50.
Frame 2 will bring it to 75.
Now the distance is 25. Half of that is 12.5, so the new position will be 87.5.
Following the sequence, the position will be 93.75, 96.875, 98.4375, and so on. After 20 frames, it will be 99.999809265.
As you can see, it gets closer and closer but never actually reaches the target—theoretically. However, things are a bit different when you examine what the code does. Visually, it comes down to the question:
“How small can you slice a pixel?” And that answer is not specified in the HTML5 Canvas specification, rather, it's defined by the browser implementation. Vendors could implement a high-resolution canvas with a precision to many decimal places, but average viewer would have a very difficult time discerning a pixel difference at 10 steps, meaning within 0.1.
In this example, the closest we can get the
position
to 100 after 20 steps is 99.99990463256836.var position = 0, target = 100;
for (var i = 0; i < 20; i++) { console.log(i + ": " + position);
position += (target - position) * 0.5;
}
This loops through 20 times, moving the
position
half the distance to thetarget
; it's just basic easing code. We're only interested in printing the positions, not actually seeing the motion. But, what you’ll find is that by the eleventh iteration, theposition
has reached 99.9, and when viewing this demonstration on the canvas, that’s as close as our ball will get.176
Even though the ball on the canvas is not getting any closer visually, mathematically it will still never actually reach its target. So, if you’re doing a simple comparison, as in the previous example, your easing code will never get shut off. What you need to decide is: “How close is close enough?” This is determined by whether the distance to the target is less than a certain amount. For many examples in this book, if an object is within a pixel of its target, it’s safe to say it has arrived, and the easing code will be turned off.
If you are using two dimensions, you can calculate the distance using the formula introduced in Chapter 3:
var distance = Math.sqrt(dx * dx + dy * dy);
If you have a single value for distance, as when you are moving an object on a single axis, you need to use the absolute value of that distance, as it may be negative. You can do this by using the
Math.abs
method.Here’s a simple example to demonstrate turning off easing (
03-easing-off.html
):<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Easing Off</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<textarea id="log"></textarea>
<script src="utils.js"></script>
<script src="ball.js"></script>
<script>
window.onload = function () {
var canvas = document.getElementById('canvas'), context = canvas.getContext('2d'),
animRequest = window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
var dx = targetX – ball.x;
if (Math.abs(dx) < 1) { ball.x = targetX;
window.cancelRequestAnimationFrame(animRequest);
log.value = "Animation done!";
} else {
var vx = dx * easing;
ball.x += vx;
177
This exercise expands the easing formula a bit to first calculate the distance, since you’ll need this to see whether easing should be stopped. Perhaps now you can see why you need to use the absolute value of
dx
. If the ball were to the right of the target,dx
would be a negative number, the statementif(dx < 1)
would evaluate astrue
, and that would be the end of things. By usingMath.abs
, you make sure that the actual distance is less than 1. You then place the ball where it is trying to go and disable the motion code.Each animation frame request is stored in the variable
animRequest
. When we want to turn off the animation loop, we pass this variable as a parameter towindow.cancelRequestAnimationFrame
. If this function is not available in your browser natively, then add this cross-browser implementation to the fileutils.js
:if (!window.cancelRequestAnimationFrame) {
window.cancelRequestAnimationFrame = (window.cancelAnimationFrame ||
window.webkitCancelRequestAnimationFrame ||
Remember that if you are doing something like a drag-and-drop with easing, you’ll want to re-enable the motion code when the ball is dropped.