How To Create HTML5 Canvas Animated Transitions using KineticJS
Azoft Blog How To Create HTML5 Canvas Animated Transitions using KineticJS

How To Create HTML5 Canvas Animated Transitions using KineticJS

By Anton Kavytin on August 22, 2013

Until recently, interactive web graphics usually meant Flash. Today, we have a number of alternative technologies, HTML5 Canvas being one of them. In this post I'll tell you how to implement an animated transition from one scene to another using KineticJS framework and show you some tricks to make working with the canvas easier and more efficient.

To see the resulting transition, take a look at this short demo.

How to make animated transitions

The solution will require several images:

1. Background image for the first scene.

KineticJS animated transition: Background image for the first sceneThe original photo by Tom Wolf | Photography

2. Blurred background image for the first scene.

KineticJS animated transition: Blurred background image for the first scene

3. Background image for the second scene.

KineticJS animated transition: Background image for the second sceneThe original photo by Tom Wolf | Photography

4. Blurred background image for the second scene.

KineticJS animated transition: Blurred background image for the second scene

An animated transition consists of the following steps:

  • Display the first scene background.
  • Display the blurred first scene background.
  • Enlarge the blurred background and move it in the appropriate direction to achieve the motion blur effect.
  • When the image is enlarged, display the blurred background for the second scene.
  • Complete the animation by displaying the non-blurred background for the second scene.

By implementing these steps we're going to achieve the smooth transition effect. To produce a reverse transition, simply execute the same steps in reverse order and with inverted parameters (zoom out instead of zooming in). 

Now, let's see how to implement all of this using kinetic.js.

var carAnimationSettings = { // options that affect transition from scene 1 to scene 2
		animationDuration: 0.2, // transition duration
		frames: [1, 1, 1], // how many times every layer is displayed
		beginSettings: [{}, {}, {}], // initial layer options
		framesSettings: [{ // property values for animation layers, that serve as the target values for animation algorithms
			x: -104,
			y: -120,
			width: 1824,
			height: 507 * 1824 / 1024
		}, {}, {}],
	},
	carBackAnimationSettings = {// options that affect transition from scene 2 to scene 1
		animationDuration: 0.2,
		frames: [1, 2, 3],
		beginSettings: [{}, {}, {
				x: -104,
				y: -120,
				width: 1824,
				height:  507 * 1824 / 1024
			}],
		framesSettings: [
			{}, {}, {
				x: 0,
				y: 0,
				width: 1024,
				height: 507
			}],
	};
	/* draw main elements */
/* initialising the scene */
	var stage = new Kinetic.Stage({
		container: 'canvasWrap',
		width: 1024,
		height: 512
	});
/* initialising the main layer */
	var layer = new Kinetic.Layer();
	gLayer = layer;
/* scene #1 */
	var mainLayer = new Kinetic.Image({
		x: 0,
		y: 0,
		image: '1.jpg', // non-blurred background for the 1st scene
		width: 1024,
		height: 512
	}); 
// layer with a picture of an automobile, we'll connect the animation events to it later
	var car_light = new Kinetic.Image({
		x: 0,
		y: gSimCarPosition,
		image: gAllImagesDOM['commonImages'][6],
		opacity: 0
	});
	layer.add(mainLayer); 
	layer.add(car_light); 
// 2.jpg '“ 1st scene with blur
// 3.jpg '“ 2nd scene with blur
// 4.jpg '“ 2nd scene without blur
	gAllImages = { // List of images for preloading
		'animationElements': {
			'carAnimation': ['2.jpg', '4.jpg', '5.jpg'] // scene 2
		}		
	};
	gAnimationShapes = {};
// create additional layers for smooth transitions from scene 1 to scene 2
// this loop can process any number of images
	for( var i in gAllImages ['animationElements'] ) {
		animationShapes[i] = [];
		for( var j = 0, l = gAllImages ['animationElements'][i].length; j < l; j++ ) {
			var shapeImage = new Kinetic.Image({
				image: gAllImagesDOM['animationElements'][i][j],
				opacity: 1
			});
			gAnimationShapes[i].push(shapeImage); 
			layer.add(shapeImage);
			shapeImage.hide(); // hide the layer
		}
	}
	stage.add(layer); 
// connect the click event to the automobile picture
	car_light.on('click touchstart', function() {
		onLightedClick(animationShapes['carAnimation'], carAnimationSettings, carBackAnimationSettings, layer, {
			'elem': car_light,// element to hide after the animation ends
			'button': '#backButton',	// HTML element ID, that servers as a trigger for reverse animation
			'beforeCallback': function() {
			// do something before beginning the transition from scene 1 to scene 2
			},
			'afterCallback': function() {
			// do something after ending the transition from scene 1 to scene 2
			},
			'afterBeforeCallback': function() {
			//do something before beginning the transition from scene 2 to scene 1
			},
			afterAfterCallback: function() {
			// do something after ending the transition from scene 2 to scene 1
			}
		});
	});	

/* function to start the animation */
function onLightedClick(animationShapes, animationSettings, animationBackSettings, layer, options) {
	var elem = hasProperty(options, 'elem') ? options['elem'] : null, //element for lighting
	gTransitionAnimation.mount(animationShapes, $.extend(animationSettings, {'layer': layer}), function() {
			if( hasProperty(options, 'beforeCallback') && !empty(options['beforeCallback']) ) {
				options['beforeCallback'].call(this);
			}
			if( !empty(elem) ) {
				elem.hide();
			}
		},
		function(lastShape) {/ /lastShape - the last layer in our animation
			if( hasProperty(options, 'afterCallback') && !empty(options['afterCallback']) ) {
				options['afterCallback'].call(this, lastShape);
			}
			if( !empty(animationBackSettings) ) {
				if( hasProperty(options, 'button') ) {
					var backButton = $(options['button']);
					backButton.show();
					backButton.on('click touchstart', function() {
						gTransitionAnimation.mount(animationShapes.slice(0).reverse(), $.extend(animationBackSettings, {'layer': layer}),
							function() {
								if( hasProperty(options, 'afterBeforeCallback') && !empty(options['afterBeforeCallback']) ) {
									options['afterBeforeCallback'].call(this);
								}
							},
							function(lastShape_back) { // lastShape_back - the last layer in reverse animation
								if( !empty(elem) ) {
									elem.show();
								}
								lastShape_back.hide();
								layer.draw();
								backButton.unbind('click touchstart');
								backButton.hide();
								if( hasProperty(options, 'afterAfterCallback') && !empty(options['afterAfterCallback']) ) {
									options['afterAfterCallback'].call(this);
									}
							}
						);
						return false;
					});
				}
			}
		});
}

/* Animate transitions between screens */
function transitionAnimation() {
	this.options = {}; //main options of transitionAnimation
	var defaultOptions = {
			imageIndex: 0, //current index of playing animation
			animationDuration: 1, // duration of the animation
			layerAnimation: null, // layer to draw the animation on
			frames: [],// list of durations for playing specific animations
			beginSettings: [], // initial settings for the animation
			framesSettings: [], // frame settings for the animation
			oneFrame: 0,// duration of 1 frame
			framesNumber: 0, // total count of frames
			imagesNumber: 0, // number of animated images
			callback: null, // function to execute when the animation ends
			layer: null // layer 
		},
		This = this, //link to object
		beforeAnimate = null, // function to call before the animation starts
		afterAnimate = null,// function to call after the animation ends
		shapeImages = null;// list of kineticjs shapes

the following code initializes main animation properties and executes the transition between the scenes
	
	this.mount = function(_shapeImages, optionsList, _beforeAnimate, _afterAnimate) {
		This.resetOptions();		
		beforeAnimate = _beforeAnimate;
		afterAnimate = _afterAnimate;
		for( var i in optionsList ) {
			This.options[i] = optionsList[i];
		}
		var options = This.options;
		This.options.imagesNumber = options.frames.length;		
		for( var i = 0, l = This.options.imagesNumber; i < l; i++ ) {
			This.options.framesNumber += options.frames[i];
		}
		This.options.oneFrame = options.animationDuration / options.framesNumber;	
		shapeImages = _shapeImages;
		for( var i = 0; i < options.imagesNumber; i++ ) {
			This.options.framesSettings[i]['duration'] = options.frames[i] * options.oneFrame;
			This.options.framesSettings[i]['callback'] = function() {
				options = This.options;
				var imageIndex = options.imageIndex;
				if( imageIndex < options.imagesNumber ) {
					if( This.options.imageIndex != 0 ) {
						shapeImages[imageIndex - 1].hide();
					}
					_doTransition(false);
				} else {
					if( !empty(afterAnimate) ) {
						afterAnimate.call(this, shapeImages[imageIndex - 1]);
					}
				}
			};
		}
		if( !empty(beforeAnimate) ) {
			beforeAnimate.call(this);
		}
		_doTransition(true);
	}
	
	function _doTransition(increment) {
		var options = This.options,
			imageIndex = options.imageIndex,
			curShape = shapeImages[imageIndex];
		curShape.show();
		if( hasProperty(options.beginSettings[imageIndex], 'width') ) {
			curShape.setWidth(options.beginSettings[imageIndex]['width']);
		}
		if( hasProperty(options.beginSettings[imageIndex], 'x') ) {
			curShape.setX(options.beginSettings[imageIndex]['x']);
		}
		if( hasProperty(options.beginSettings[imageIndex], 'y') ) {
			curShape.setY(options.beginSettings[imageIndex]['y']);
		}
		curShape.parent.draw();
		if( elemLength(options.framesSettings[imageIndex]) == 2 ) {
			setTimeout(options.framesSettings[imageIndex]['callback'], This.options.frames[imageIndex] * options.oneFrame);
			if( increment ) {
				This.options.imageIndex++;
			}
		} else {
			curShape.transitionTo(options.framesSettings[imageIndex]);
		}
		if( !increment ) {
			This.options.imageIndex++;
		}
	}
	
	this.resetOptions = function() {
		This.options = $.extend({}, defaultOptions);		
	}
}

Here's a little overview of how the animation is produced:

  1. Display the first layer and assign its initial options: the position of the top-left corner, size and background.
  2. If target options are available, calculate the steps to slowly transform the layer to the target dimensions.
  3. After the first layer is displayed, show the second layer similarly to step 1. Then run step 2 for the second layer.
  4. Launch the final callback after the animation ends.

This mechanism works in FF, Chrome, IE9 and iPad.

More tricks

We can connect mouseover/mouseout events to the layer or display a tooltip with some text. The example below shows how to slowly raise the layer opacity to 1 when the mouse enters the layer and put down the opacity back to 0 when the mouse leaves it. In practice, this code may be used to implement layer highlighting.

/* mouseover/mouseout events for canvas element */
// _layer - layer to draw the animation on
// _lighted_layer - tint layer
// tooltipId - the ID of HTML element, which serves as the tooltip

function mouseEvents(_layer, _lighted_layer, tooltipId) {
	var animationDuration = 300, // duration of the animation
		layerAnimation = null; // layer to draw the animation on
	_lighted_layer.on('mouseover', function(event) {
		document.getElementById(tooltipId).style.display = 'block';
		document.body.style.cursor = 'pointer';
		if( layerAnimation !== null ) {
			layerAnimation.stop();
		}
		layerAnimation = new Kinetic.Animation(function(frame) {
				if (frame.time > animationDuration) {
					layerAnimation.stop();
					_lighted_layer.setOpacity(1);
				} else {
					_lighted_layer.setOpacity(frame.time / animationDuration);
				}
			}, _layer);
		layerAnimation.start();
	});
	_lighted_layer.on('mouseout', function(event) {
		for( var i = 0, tooltips = document.getElementById('tooltips').querySelectorAll('.tooltip'), l = tooltips.length; i < l; i++ ) {
			tooltips[i].style.display = 'none';
		}		
		document.body.style.cursor = 'default';
		if( layerAnimation !== null ) {
			layerAnimation.stop();
		}
		layerAnimation = new Kinetic.Animation(function(frame) {
				if(!gMasked) {
					if ( 0 > animationDuration - frame.time) {
						_lighted_layer.setOpacity(0);
						layerAnimation.stop();
					} else {
						_lighted_layer.setOpacity((animationDuration - frame.time) / animationDuration);
					}
				}
			}, _layer);
		layerAnimation.start();
	});
	_lighted_layer.on('mousemove', function(event) {
		var tooltips_style = document.getElementById('tooltips').style;
		tooltips_style.top = event.clientY + 20 + 'px';
		tooltips_style.left = event.clientX + 10 + 'px';
	});
}

The tooltip element code is:

<div id="tooltips">
    	<div id="tooltip1" class="tooltip">Tooltip 1</div>
    	<div id=" tooltip2" class="tooltip"> Tooltip 2</div>
    	<div id=" tooltip3" class="tooltip"> Tooltip 3</div>
 </div>

To call the handlers use:

mouseEvents(layer, car_light, '˜tooltip1');
VN:F [1.9.22_1171]
Rating: 4.3/5 (7 votes cast)
VN:F [1.9.22_1171]
Rating: +5 (from 5 votes)
How To Create HTML5 Canvas Animated Transitions using KineticJS, 4.3 out of 5 based on 7 ratings



Request a Free Quote
 
 
 

Please enter the result and submit the form

Content created by Anton Kavytin