Forum Discussion
An Easy-to-Use Animated Menu Using GSAP
Introduction
(2024/07/01 - Updated some details and the attachment)
Inspired by a recent post about replicating an animated carousel menu in Storyline, I came up with this solution. As it is pretty easy to implement and quite flexible, I thought I would share the results with anyone who wants to use or modify it.
The original can be seen here. While this can be replicated in Storyline with animation paths and some creative layering, it seems this would quickly become difficult to manage. The approach I settled on, once you add a bit of JavaScript on the Slide Master and a few SL variables, only requires you to add some menu items and a shape path to your slide.
The menu items could be a variety of things like images, shapes, text, videos or something similar. These get labeled as menu items in the accessibility tag.
The shape path can be a closed shape, like a circle or square, or open like a curve or scribble. These ae labeled as paths.
After specifying the starting characteristics for your menu layout, a simple variable change will move your menu. It will automatically resize with your slides. Very easy.
For those who just want the files, you can skip the details below. For the rest, I will describe how it works in case you want to make any changes for your own uses. Aside from menus, I could see this as useful for creating animated indicators, moving pieces along a 3-D gameboard path, or animating informational displays, among other things.
How it Works
My background with GSAP is limited, and mostly comes from reading posts from Math Notermans and the GSAP website. As Math has pointed out, Storyline uses GSAP behind the scenes to handle a lot of its animation behaviors. Since it is included, you can access GSAP directly in your own JavaScripts. You can also include any of the freely available GSAP plugins for extra functionality. Storyline only includes basic GSAP, so I added the MotionPath plugin using a technique described in one of Math’s many posts. This plugin allows you to utilize paths, like those you might find within SVG graphics, to control the motion of other objects.
Each of the Storyline slides uses the Document Object Model (DOM) to define the HTML document, and all the elements it contains. When you add a shape, like the ellipse used in in the included example files, HTML document is modified, adding an SVG graphic and some additional elements that wrap around it to keep things organized. The general layout is:
<div … data-acc-text=”menuPath” …>
(this is the wrapper object identified by the Accessibility tag)
<div>(this a wrapper for the SVG graphic)
<svg>(this is the actual SVG graphic)
<g >(this is the stuff that makes up the parts of the SVG)
<path>(this is the information that describes the border and fill of the shape)
</g>
</svg>
</div>
</div>
An example Path might look something like this:
<path d="M944,191 C944,246.27669 885.97234,296.06897 793.22246,330.94604 " id="uniqueDomId-440" fill="none" stroke-width="8" stroke="#d11141" stroke-opacity="1" stroke-linecap="flat" stroke-linejoin="round" data-accepts="events"></path>
The path attributes include “d” for the data and several styling values like “stroke’ for color, and related values for width, opacity, and fill. We are most interested in the path data, or “d”.
The path describes the shape of the ellipse. Since we want to animate our menu items around this shape, we want to use this path data. The easiest way to do this is to follow segments of this path. Dividing the ellipse into four equal segments would allow us to distribute four menu items evenly around the ellipse. This is where the MotionPath plugin comes in handy. It provides functions that can extract path data and cut them into smaller pieces.
To keep track of these new shorter segments, we can use the fact that SVGs can hold multiple paths. If we extract the path data from our original ellipse, cut it into four equal pieces, and then replace the original path element with four new path elements, one with data for each segment, then we will still have what looks like an ellipse, just drawn in four steps instead of one.
GSAP already provides .from , .to, .fromTo, and .set functions making it easy to animate objects on your slide. These usually specify beginning or ending coordinates, or both. The MotionPath plugin adds the ability to specify a specific path to follow instead of just a target coordinate. This is how each of the menu items get moved from position to position.
It is important to keep in mind that GSAP just adjusts elements on the page AFTER the original layout was created. Storyline only knows where things were when they were originally laid out. If GSAP moves them, Storyline will not know. Not surprisingly then, if you resize your slide after using GSAP, anything you have moved will become misaligned. Storyline doesn’t know where you want them to be. You must take responsibility for that. To do so, you need a resize event handler.
A resize event handler will watch your slide, or some part of it, and act when it senses a resize event. It calls a function that you create, and this is where you will provide the details of where anything that you move should be placed. In our case, we keep track of where each menu item is along the path. Since we know where every is supposed to be, when a resize event happens, we can easily have GSAP move everything back to where we want to be using the .set function. This just moves elements to the endpoint of the path you specify.
Usage Details
To use this menu, you will need to do a few things (see the example .story file for details).
- Add the main JavaScript routine to the Master slide
- Trigger this on variable change (“triggerMenu”)
- Add all the new menu-related variables to your Storyline project
Now, on any slide you want to have a menu:
- Add a small piece of JavaScript to each slide containing a menu
- This just contains some variables you can use to specify how the menu looks, and where items get positioned
- Add your menu items to the slide
- Set the accessibility tag for each to represent the item order
- Label them “menuItem_1”, “menuItem_2”, etc.
- Add a shape path to the slide
- Set the accessibility tag to “menuPath”
- Group all the menu items and the shape path together
- Set the accessibility tag for the Group to “menuGroup”
- This just makes it easier to move your menu around and allows the script to hide the menu before it is initialized
- This also allows you to not worry about where the menu items are placed. Just place the shape path where you want it and put the menu items anywhere on the slide. They will be moved into position automatically
- Trigger the menu movement and direction by adjusting a couple of variables in Storyline
When changing slides, you must disable the resize event handler before changing slides because the menu items will no longer be available to update. An error will occur.
- Before leaving slide, set “menuEndResizer” = true
- Toggle “triggerMenu” to remove the resizer
- Now you can change slides
Menu Items
For menu items, you can use elements that can be grouped. Don’t use elements with states that can change on hover or click, like a button. This code will not currently handle these state changes, and the items will lose their positions. You could modify the code to accommodate states if you really wanted to. Menu items used in the example file include images, text boxes, shapes, characters, icons, and even videos.
Menu Paths
For menu paths, you can use closed shapes like circles, squares, triangles, etc., and open curves and scribbles. Don’t use a straight-line shape. Use a curve drawn as a straight line instead. Items will jump from the end to the beginning of an open curve.
To help with positioning, you can set the ‘menuDebug” SL variable to true to see the individual path segments in different colors.
Based on the settings you specify, the script will indicate which menu item is foremost, in case you want to restrict any triggers you set up on them.
Settings Variables
For each menu you want to use, copy the settings script, and change the values accordingly. These include:
menuItemCount
How many items are in the menu (make sure each has an accessibility label).
scaleFactorTo, opacityFactorTo, zindexFactorTo
Specifies the target styling for each menu item at the END of each path segment. Path segments are indexed starting at 0. The staring point may differ between shapes. Use the debug feature to see the segments. They start with Red, and then proceed through Green, Blue, Orange, Yellow.
itemPathMap
This indicates on which segment each menu item should start. When initialized, each item will be placed at the end of its starting path segment.
pathSegment
if pathSegment[0] = “auto”, then the script will divide the path evenly to match the number of menu items. This works fine for something like an ellipse. If you want to match item positions with vertices or specify specific stopping points to match your onscreen graphics, then you will need to specify your own segments.
Set pathSegment with pairs of fractional values, indicating the starting and stopping point for each segment, in order. The values are the fraction of the original path length. For example, an evenly split four segment path would look like [0,0.25,0.25,0.5,0.5,0.75,0.75,1].
menuFrontPosition
Specifies which position you consider foremost. Used to set the variable which indicates which menu item is foremost.
menuDebug
Turns on the color segment override for the menu path, making them visible. Useful for properly adjusting your positions manually.
Other Variables to Include in Storyline
frontMenuItem - Value, Indicates which item is currently foremost
javaScriptsLoaded - T/F, Indicates when all the files in the web object are loaded
menuForward - T/F, Set to true to move menu forward, false to go backward
menuResizer - Text, a reference to the resize observer so we can cancel it later
originalPath - Text, a reference to the original path in case we need to reset it
The Rest
For more details, see the comments inside the scripts. The main script goes on the master base slide. The settings script goes on each slide with a menu. The master base slide also contains a script for loading the GSAP MotionPath plugin. It also registers the plugin so you can use it in other scripts. The actual GSAP plugin code is inside the web object, in Scene 2.
The the JavaScript for the MotionPath plugin is available on the GSAP plugin page. It is free, under the publicly available list. I prefer to include the actual code with my projects, so I know exactly what I am using. You never know what version changes might do to your existing projects. The current version is under the WO folder in the zipped project file.
If changes are made to this file, other files are added, or the web object is deleted and replaced, then you will need to update the woFolder variable on the master slide. This variable indicates the name of the folder holding the web object files.
To refresh this variable, publish only Scene 2. Click the Load WO button. A new window or tab will open. Look at the URL address. The name of the web object folder is the portion of the URL BETWEEN “WebObjects/” and “/index.html”. For example, the URL ending with “…/story_content/WebObjects/6Au3D8qJknD/index.html” indicates that the web object folder is called “6Au3D8qJknD”. Copy this value and assign it to woFolder in the master slide trigger.
- Nathan_HilliardCommunity Member
I added a few details and corrected an error I noticed in the .story file.
- MathNotermans-9Community Member
Nice and complete explanation Nathaniel :-)
- AngeCommunity Member
Super cool Nathanial. Thank you for laying out your process.
- EswarkumarKanthCommunity Member
Thank you for sharing. When I resize the window, the X and Y positions of the elements change. Is it possible to fix this issue? Resolving it might help prevent errors.
- Nathan_HilliardCommunity Member
I downloaded and republished to test this and it still seemed to work fine with Chrome and Firefox. Perhaps you are not using the latest SL version? Older versions may use a different html structure. If you are trying to put this into your own project, make sure you include the resize observer function so that everything is redrawn properly on resize.
- EswarkumarKanthCommunity Member
Yes, thanks for the reply. I checked in the latest version, and it is working fine.