In our previous tutorial Dragging objects – a very simple HTML5 Canvas example, we looked at a simple example of dragging objects in the canvas with mouse interactions. Here we will extend this example by adding some new behaviors. First, we will change the layering order of the objects every time an object is dragged, so that the currently dragged object appears on top of all the others. Also, we will change the animation technique in such a way to create a smooth motion of the object while the mouse is dragging.

Click here or on the screenshot below to see the demo.


SmoothDraggingscreencap

Download

Download the complete, well-commented source code here: SmoothDragging.zip

About the Code

Some of the details behind the code were explained in our earlier posting, so here we will concentrate just on how the example has been extended. You should also download the code (or request your browser to show the source on the demo page) to see how it is all put together.

Relayering

The display objects are represented by JavaScript Objects stored in an array, and every time the canvas is redrawn, the objects in this array are drawn in their array order. Relayering is easy to achieve by simply reordering this array:

shapes.push(shapes.splice(dragIndex,1)[0]);

If you are not familiar with array methods, here is what is happening in this line: the splice method splices out an array containing one element at the position dragIndex, and this one element (which is at position 0 in this sub-array) is put back into the array at the end using the push() method.

Animating with a Timer

In our previous example, the animation of dragging the object was created by updating the screen every time a mousemove event occured (and after a mousedown over a display object caused dragging to be in effect). Here we are doing something different: as soon as a mousedown occurs over one of our display objects, we start a timer, and on every “tick” of this timer we update the screen. The timer is set with the code

timer = setInterval(onTimerTick, 1000/30);

which uses a time interval of 1000/30 milliseconds, creating a framerate of 30 frames-per-second. Note that the timer is given a name (a variable declared earlier in the code) so that the timer can be cancelled later when no longer needed. Cancelling the timer is accomplished with the code

clearInterval(timer);

Smooth Motion

Getting the dragged object to move smoothly is done with a classic technique. The mouse position defines the position where the user wants the object to be — call this the “target” position. But instead of moving to the target position automatically, we have the object gradually move towards that position. We define an amount easeAmount, which should be a number between 0 and 1, and on each redrawing of the screen we move the object only this much closer to the target position. In our example here, we set easeAmount to 0.2, which means that on each screen update the object will move only 20% of the distance from its current position towards the target.

Below is the code, found in the onTimerTick function called on every tick of the timer, which creates this motion. Note that the currently dragged object is shapes[numShapes-1], the last one in the array:

shapes[numShapes-1].x = shapes[numShapes-1].x + easeAmount*(targetX - shapes[numShapes-1].x);

shapes[numShapes-1].y = shapes[numShapes-1].y + easeAmount*(targetY - shapes[numShapes-1].y);

Stopping the Motion

We don’t stop the motion of the currently dragged object if the mouse button is lifted, because the object may not yet be at the position desired by the user. Instead, we wait until the object reaches its destination. When the x and y coordinates of the object are within 0.1 pixel of the target position, and the mouse button is up (signaling that the user is no longer actively moving the object), we stop the timer and snap the object to the exact target position:

if ((!dragging)
    &&(Math.abs(shapes[numShapes-1].x - targetX) < 0.1)
    &&(Math.abs(shapes[numShapes-1].y - targetY) < 0.1)) {
       shapes[numShapes-1].x = targetX;
       shapes[numShapes-1].y = targetY;
       //stop timer:
       clearInterval(timer);
}

Using a gradient fill

For a little more visual interest, the circular display objects are filled with an offset gradient, which makes them appear to be three-dimensional spheres. These display objects are stored as abstract JavaScript Objects, each of which simply records the position and radius of the object, along with two colors to use for the beginning and ending color of the radial gradient. The colors are chosen randomly. The base color for the object is chosen by randomly selecting an integers for the red, green, and blue color components, and combining them into one string in the rgb() color declaration style:

r1 = Math.floor(Math.random()*200);
g1 = Math.floor(Math.random()*200);
b1 = Math.floor(Math.random()*200);
color1 = "rgb(" + r1 + "," + g1 + "," + b1 +")";

Note that we are letting the color components range up to 200 instead of the usual maximum 255, in order to ensure the base colors will not be too bright. The brightest color in the gradient will be a multiple (greater than 1) of the base color, which will cause it to be brighter. We are also careful to ensure that the multiplied values do not exceed 255:

r2 = Math.min(Math.floor(gradFactor*r1),255);
g2 = Math.min(Math.floor(gradFactor*g1),255);
b2 = Math.min(Math.floor(gradFactor*b1),255);
color2 = "rgb(" + r2 + "," + g2 + "," + b2 +")";

The multiplier gradFactor was defined earlier in the code to be the number 2, but you could experiment with higher or lower numbers to produce more or less pronounced gradients. These two colors are stored as part of the Object representing a display object:

tempShape = {x:tempX, y:tempY, rad:tempRad, gradColor1:color1, gradColor2:color2};

Later when it is time to draw the objects, here is how we apply the gradient. First we create a gradient based on the position and radius of the object, which is stored in the array called shapes:

rad = shapes[i].rad;
x = shapes[i].x;
y = shapes[i].y;
grad = context.createRadialGradient(x-0.33*rad, y-0.33*rad, 0, x-0.33*rad, y-0.33*rad, 1.33*rad);
grad.addColorStop(0,shapes[i].gradColor2);
grad.addColorStop(1,shapes[i].gradColor1);

The createRadialGradient method takes three parameters, which define a beginning and ending circle where the radial gradient should begin and end. The format is


createRadialGradient(startX, startY, startRadius, endX, endY, endRadius);

Here, startX and startY define the starting circle center, and startRadius its radius. The next three variables similarly define the ending circle. Colors are added to this gradient with the addColorStop method of the gradient object as seen above. The format is

addColorStop(ratio,color);

The variable ratio should range from 0 to 1, and it defines the position along the gradient where this color should occur, where 0 represents the beginning of the gradient and 1 represents the end. Note that in our example, the gradient starts out with radius zero at a position offset by a third of the radius from the center of the circle. At this position, the gradient will be at its brightest value because of the way the colors were created above. The gradient then reaches its final darkest color at a circle with radius equal to 1.33 times the radius of the circular display object to be drawn (this will cause the final color to be just visible at the bottom right edge of the display object).

Applying the gradient during drawing is easy; we simply set the fillStyle to equal the gradient object, and draw our circle, making sure to call the fill() method when we are done:

context.fillStyle = grad;
context.beginPath();
context.arc(x, y, rad, 0, 2*Math.PI, false);
context.closePath();
context.fill();