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...
- spinToStop - the wheel spins over time to a stop, normally starting fast then slowing before stopping
- spinOngoing - the wheel keeps spinning forever unless the animation is specifically paused or stopped
- spinAndBack - the wheel spins in one direction and then back in the other direction, possibly repeating
- custom - by itself this option does nothing; you must set at least a couple of animation parameters (propertyName, propertyValue) besides the duration
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...
- duration - this must always be specified in seconds. How long you want the animation to be
- spins - the number of times the wheel is to rotate past 360 degrees (i.e. complete rotations)
- repeat - times the animation is to repeat, set to -1 for animation to repeat forever
- easing - the easing function to use to slow or speed up the animation over time or keep it constant
- See http://greensock.com/ease-visualizer for available easing functions
- direction - clockwise or anti-clockwise (only used for spinning animations)
- stopAngle - the exact angle around the wheel to stop at. If null for spinToStop type a random stop angle will be chosen.
- yoyo - if set to true the animation will reverse back to the start after completing
- propertyName - the name of the property of the wheel to animate. For all spin animation types this is set to rotationAngle, for custom animations you could set this to any numeric property of the wheel, for example the outerRadius, centerX, centerY, to animate those aspects of the wheel.
- propertyValue - for custom animations. The value you want for the property at the end of the animation.
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> |
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> |
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> |
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> |
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> |
Playing, Pausing, Resuming, and Stopping animations
To control the animation, call these functions on your wheel object. For example theWheel.startAnimation();
- To play the animation call the startAnimation()
- To pause the animation call pauseAnimation()
- To resume the animation call resumeAnimation()
- To stop the animation call stopAnimation(canCallback)
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...
- callbackBefore - called before the wheel is drawn each animation loop, use this to draw anything on the canvas you want to appear BEHIND (or beside) the wheel
- callbackAfter - called after the wheel has been drawn each animation loop, use this draw anything on the canvas you want to appear ON TOP of (or beside) the wheel
- callbackFinished - called once when the animation has finished, useful for doing something once the animation (spinning etc) has ended.
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> |
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> |
Previous: < #11 Animation Introduction and Spinning Basics |
Next: #13: Getting the segment (prize) pointed to > |