Winwheel.js tutorial: #12 Animation - More Details


In this tutorial we will go in to more details of the animation properties and options and explore some other animation effects than simply spinning to a stop. Its a bit of beast, so if it's your first time using Winwheel.js or you are not interested in any animation other than spinning your wheel, you may want to skip to the next tutorial.



An important concept or 3


There is an important concept which it is helpful to understand; The Greensock Animation Platform (TweenMax) that Winwheel.js leverages can animate any numeric property over time. As many of the properties of the wheel are numeric such as the rotation angle, x and y, radius etc, this is perfect for our needs and why I chose it.


You must always specify a duration for the animations. As highlighted above, TweenMax animates numeric propteries over time so you need to specify what that time duration is.


Another thing to remember is that I have created a few animation types in Winwheel.js as wrappers over the Greensock Animation TweenMax.js to make animating the wheels you create easier. All but one of these are spinning related.



Winwheel.js Animation Types and Properties


There are a number of properties which control the animation, many you don't normally need to worry about because for each Type of animation there are sensible defaults and you would just specify one or two of these, but I will detail all of them here.


There are currently 4 animation types built in to Winwheel Library. To use them set the type property to specify which one you want...


As well as these main "Types" of animation, there are a number of animation parameters that you should or could set which will affect how the animation works, these are...



Property defaults for each animation type


Let's have a look at what the defaults are for each of the animation types so you can get a feel for how the properties are used, you could then experiment with different values for them in your project and see how it affects the animation. Click the Play/Stop button under each example to see the animation in action.


Spin to stop

<script>
    let spinStopWheel = new Winwheel({
        'numSegments' : 4,
        'segments'    :
        [
            {'fillStyle' : '#eae56f', 'text' : 'Segment 1'},
            {'fillStyle' : '#89f26e', 'text' : 'Segment 2'},
            {'fillStyle' : '#7de6ef', 'text' : 'Segment 3'},
            {'fillStyle' : '#e7706f', 'text' : 'Segment 4'}
        ],
		'animation' :
		{
			// Must be specified...
			'type'     : 'spinToStop',
			'duration' : 5,

			// These are the defaults, all optional...
			'spins'        : 5,
			'easing'       : 'Power4.easeOut',
			'stopAngle'    : null,
			'direction'	   : 'clockwise',
			'repeat'       : 0,
			'yoyo'		   : false
		}
    });
</script>
Canvas not supported, use another browser.

Spin ongoing

<script>
    let spinOngoingWheel = new Winwheel({
        'numSegments' : 4,
        'segments'    :
        [
            {'fillStyle' : '#eae56f', 'text' : 'Segment 1'},
            {'fillStyle' : '#89f26e', 'text' : 'Segment 2'},
            {'fillStyle' : '#7de6ef', 'text' : 'Segment 3'},
            {'fillStyle' : '#e7706f', 'text' : 'Segment 4'}
        ],
		'animation' :
		{
			// Must be specified...
			'type'     : 'spinOngoing',
			'duration' : 5,

			// These are the defaults, all optional...
			'spins'        : 5,
			'easing'       : 'Linear.easeNone',
			'direction'	   : 'clockwise',
			'repeat'       : -1,
			'yoyo'		   : false
		}
    });
</script>
Canvas not supported, use another browser.

Spin and Back

<script>
    let spinAndBackWheel = new Winwheel({
        'numSegments' : 4,
        'segments'    :
        [
            {'fillStyle' : '#eae56f', 'text' : 'Segment 1'},
            {'fillStyle' : '#89f26e', 'text' : 'Segment 2'},
            {'fillStyle' : '#7de6ef', 'text' : 'Segment 3'},
            {'fillStyle' : '#e7706f', 'text' : 'Segment 4'}
        ],
		'animation' :
		{
			// Must be specified...
			'type'     : 'spinAndBack',
			'duration' : 5,

			// These are the defaults, all optional...
			'spins'        : 5,
			'easing'       : 'Power2.easeInOut',
			'stopAngle'    : 0,
			'direction'	   : 'clockwise',
			'repeat'       : 1,
			'yoyo'		   : true
		}
    });
</script>
Canvas not supported, use another browser.

Custom

There is no defaults for the properties in this type of animation, you must set them based on the type of animation you want to do. As well as the type and duration, you must specify the propertyName that you want to animate over time and propertyValue which is the value that you want the property to be at the end of the animation.


This option is where you can do some really interesting things as you can choose to animate other properties of the wheel besides the rotationAngle. Here is a couple of examples of this.


In the first one I animate the inner radius to get the wheel to open and close like a portal.

<script>
    let radiusAnimWheel = new Winwheel({
        'numSegments' : 4,
        'segments'    :
        [
            {'fillStyle' : '#eae56f'},
            {'fillStyle' : '#89f26e'},
            {'fillStyle' : '#7de6ef'},
            {'fillStyle' : '#e7706f'}
        ],
		'animation' :
		{
			// Must be specified...
			'type'     : 'custom',
			'duration' : 3,

			// For custom propertyName and propertyValue
			// must be specified. Others as needed.
			'propertyName'  : 'innerRadius',
			'propertyValue' : 100,
			'easing'        : 'Power2.easeInOut',
			'repeat'        : -1,
			'yoyo'		    : true
		}
    });
</script>
Canvas not supported, use another browser.

In the second example I animate the centerX and centerY getting the wheel to move around the canvas. I use the callbackFinished property of the animation to call the nextAnimationStep() function I created when the animation finishes to change the animation properties and then start a new animation.

<script>
    let xyAnimStep = 1;

	let xyAnimWheel = new Winwheel({
		'numSegments' : 4,
		'outerRadius' : 30,
		'centerX'	  : 55,
		'centerY'     : 55,
		'segments'    :
		[
			{'fillStyle' : '#eae56f'},
			{'fillStyle' : '#89f26e'},
			{'fillStyle' : '#7de6ef'},
			{'fillStyle' : '#e7706f'}
		],
		'animation' :
		{
			'type'             : 'custom',
			'duration'         : 2,
			'propertyName'     : 'centerX',
			'propertyValue'    : 245,
			'easing'           : 'Power3.easeInOut',
			'callbackFinished' : 'nextAnimationStep()'
		}
	});

	// Changes the animation once the current one has finished.
	function nextAnimationStep()
	{
		xyAnimStep ++;

		if (xyAnimStep > 4) {
			xyAnimStep = 1;
		}

		// Set up the new animation as needed.
		if (xyAnimStep == 1) {
			xyAnimWheel.animation.propertyName  = 'centerX';
			xyAnimWheel.animation.propertyValue = 245;
			xyAnimWheel.animation.easing = 'Power3.easeInOut';
		} else if (xyAnimStep == 2) {
			xyAnimWheel.animation.propertyName  = 'centerY';
			xyAnimWheel.animation.propertyValue = 545;
			xyAnimWheel.animation.easing = 'Bounce.easeOut';
		} else if (xyAnimStep == 3) {
			xyAnimWheel.animation.propertyName  = 'centerX';
			xyAnimWheel.animation.propertyValue = 55;
			xyAnimWheel.animation.easing = 'Power2.easeIn';
		} else if (xyAnimStep == 4) {
			xyAnimWheel.animation.propertyName  = 'centerY';
			xyAnimWheel.animation.propertyValue = 55;
			xyAnimWheel.animation.easing = 'Bounce.easeIn';
		}

		// Kick off the aniamtion.
		xyAnimWheel.startAnimation();
	}
</script>
Canvas not supported, use another browser.


Playing, Pausing, Resuming, and Stopping animations


To control the animation, call these functions on your wheel object. For example theWheel.startAnimation();


False can be passed in to the stopAnimation(canCallback) function to prevent any callback specified for when the animation ends from running. The default is true so it will callback unless you tell it not to. Callbacks are covered later in this tutorial.


Note: stopping the animation does not reset the properties of the wheel. Any property of the wheel altered during animation will remain that way after the animation has finished or been stopped. Most of a time this is a benefit, but in the case of spinning this can cause some unexpected behaviour.


For example after a spinning animation which rotates the wheel 10 times around 360 degrees (36000 will be the rotationAngle at the end) if you then do another animation to rotate the wheel 5x360 it will actually spin backwards to meet its new target angle (1800) not forwards another 5 times like some might expect. Similarly if you then spin the wheel another 5 times it may not move (much) this is because the new calculated target will be the same or very similar to the existing rotation angle.


I did look to make it so that any subsequent spins were relative to the previous one, but this introduces a whole raft of issues for being able to specify the stop angle and have it still stop there on subsequent spins. I might revisit this aspect of the code at some future point, but for now the best solution to overcome any issues with spinning the wheel multiple times is to reset the rotation angle between each spin.


There are a couple of ways you can reset the rotation angle, the first is obvious to the user by adding a reset / play again button like the example wheel included in the Winwheel.js download which resets the rotation angle to 0 and then re-draws the wheel. The other is to just to quietly set the rotation angle back to the starting value before the animation is started again. If your wheel spins quick enough at the beginning of the animation the user will probably not notice.


Tip: A way to keep the apparent angle of the wheel between spins but still resetting the rotationAngle to less than or equal 360 so the spin animations work correctly is to set the rotationAngle to the modulus of 360, for example:
theWheel.rotationAngle = theWheel.rotationAngle % 360.



Animation callbacks (hooks), and the clearTheCanvas option


There are a few places in the animation which you can hook in to by specifying functions the wheel code is to callback. These are useful if you have other things on the canvas which need to be re-drawn each loop of the animation, for example the prize pointer.


The callback hooks are as follows, for these specify the name of your own function which is to be called...


Note that in order to call a function you must put the round brackets after the function name, for example 'doSomethingWhenFinished()'. Note that the callbacks can actually just be javascript code to run at that time, for example "alert('Finished');" will work as one of the callbacks.


For an example of callbacks in use, see the next tutorial #13: Getting the segment (prize) pointed to which uses the callbackFinished to get the prize pointed to when the spinning stops.


clearTheCanvas


The clearTheCanvas animation property can be used to control if the canvas is completely cleared each animation loop or not. The default true because normally we don't want anything left over from the previous frame of the wheel animation left on the canvas when we draw this time. The clearing is done before the callbackBefore so anything you draw on the canvas with the callbackBefore will be kept when the wheel is drawn (unless the wheel is on top of your drawings).


Winwheel.clearTheCanvas vs Winwheel.Animation.clearTheCanvas: Note the wheel clearTheCanvas property is not the same as the animation clearTheCanvas property. The animation.clearTheCanvas property is only used during animation. The wheel's clearTheCanvas property is only used when the wheel is initially drawn or when you call the Winwheel.draw() function to render any changes.


If there are times when you don't want the canvas to be cleared during animation, you can set the wheel.animation.clearTheCanvas property to false. In the following example I repeat the animation of the centerX and centerY from above, but with clearTheCanvas set to false so you can see what it looks like...

<script>
    let xyNoClearAnimStep = 1;

	let xyNoClearAnimWheel = new Winwheel({
		'numSegments' : 4,
		'outerRadius' : 30,
		'centerX'	  : 55,
		'centerY'     : 55,
		'segments'    :
		[
			{'fillStyle' : '#eae56f'},
			{'fillStyle' : '#89f26e'},
			{'fillStyle' : '#7de6ef'},
			{'fillStyle' : '#e7706f'}
		],
		'animation' :
		{
			'type'             : 'custom',
			'duration'         : 2,
			'propertyName'     : 'centerX',
			'propertyValue'    : 245,
			'easing'           : 'Power3.easeInOut',
			'callbackFinished' : 'nextNoClearAnimationStep()',
			'clearTheCanvas'   : false
		}
	});

	// Changes the animation once the current one has finished.
	function nextNoClearAnimationStep()
	{
		xyNoClearAnimStep ++;

		if (xyNoClearAnimStep > 4) {
			xyNoClearAnimStep = 1;
		}

		// Set up the new animation as needed.
		if (xyNoClearAnimStep == 1) {
			xyNoClearAnimWheel.animation.propertyName  = 'centerX';
			xyNoClearAnimWheel.animation.propertyValue = 245;
			xyNoClearAnimWheel.animation.easing = 'Power3.easeInOut';
		} else if (xyNoClearAnimStep == 2) {
			xyNoClearAnimWheel.animation.propertyName  = 'centerY';
			xyNoClearAnimWheel.animation.propertyValue = 545;
			xyNoClearAnimWheel.animation.easing = 'Bounce.easeOut';
		} else if (xyNoClearAnimStep == 3) {
			xyNoClearAnimWheel.animation.propertyName  = 'centerX';
			xyNoClearAnimWheel.animation.propertyValue = 55;
			xyNoClearAnimWheel.animation.easing = 'Power2.easeIn';
		} else if (xyNoClearAnimStep == 4) {
			xyNoClearAnimWheel.animation.propertyName  = 'centerY';
			xyNoClearAnimWheel.animation.propertyValue = 55;
			xyNoClearAnimWheel.animation.easing = 'Bounce.easeIn';
		}

		// Kick off the aniamtion.
		xyNoClearAnimWheel.startAnimation();
	}
</script>
Canvas not supported, use another browser.


Even more advanced animations


If you want to do even more advanced animations, such as multiple properties at the same time, or a whole lot in a row, you will need to use GreekSock's Animation Platform (TweenMax) directly, or your other favourite animation system which can alter the properties of the wheel over time.


Take a look at the Getting Started with GSAP (GreenSock Animation Platform) page to learn about how to use it. Note that examples mostly use the 'Lite' versions (TweenLite), but anything you can do in the lite version you can do in the Max version (plus TweenMax has more easing functions and other useful features which is why I use it).


In order for the TweenMax's changes to the wheel to appear, you must call the draw() method of the wheel each frame or 'tick' of the animation loop.


To help you on the right track with how to do this, here is an example of using TweenMax directly and getting the Winwheel.Draw() method called each animation loop to render the changes. By default the animation will start immediately, so I have also included the code to the Play/Pause button so you can see how to create, pause, and resume the animation on demand.

<script>
    let tweenAnimState = 0;
	let tween = null;

	let tweenAnimWheel = new Winwheel({
		'numSegments' : 4,
		'outerRadius' : 30,
		'centerX'	  : 55,
		'centerY'     : 55,
		'canvasId'    : 'tweenAnimCanvas',
		'segments'    :
		[
			{'fillStyle' : '#eae56f'},
			{'fillStyle' : '#89f26e'},
			{'fillStyle' : '#7de6ef'},
			{'fillStyle' : '#e7706f'}
		]
		// Note no animation defined as part of the wheel this time.
	});

	// Create function called by the Play/Pause button.
	function playStopTweenAnim()
	{
		// If animation does not exist yet.
		if (tweenAnimState == 0) {
			// Specify the animation using TweenMax.
			tween = TweenMax.to(tweenAnimWheel, 3, {
				centerX:245, 		// Multiple properites
				centerY:545, 		// can be animated at
				rotationAngle:1800, // the same time
				repeat:-1,
				yoyo:true
			});

			// In order for changes to the wheel to appear we must
			// get the draw() function on the wheel called each frame.
			TweenMax.ticker.addEventListener("tick", myAnimationLoop);

			tweenAnimState = 1;
		} else if (tweenAnimState == 1) {
			// If playing then pause.
			tween.pause();
			tweenAnimState = 2;
		} else {
			// If paused then resume.
			tween.resume();
			tweenAnimState = 1;
		}
	}

	// Called each tick of the animation.
	function myAnimationLoop()
	{
		// Render changes to the wheel on screen.
		tweenAnimWheel.draw();
	}
</script>
Canvas not supported, use another browser.

Previous:
< #11 Animation Introduction and Spinning Basics
Next:
#13: Getting the segment (prize) pointed to >