Changing state directly from JavaScript?

Jul 04, 2015

Hello Heroes!

Maybe someone out there can help me out! I've been playing with JavaScript and Storyline, you can see an example here: http://bit.ly/1IjNEKs

But, what I can't figure out is how to access an object directly from JavaScript. I would like to change the state of a graphic from JavaScript.

In the example above, if you launch the course with adding ?test=Confused at the end of the URL link, and go to Chapter 3, the character will show the confused state. But essentially, I'm changing a variable first, which leads to a trigger to change the state. If I want ten different states, that's ten more manual triggers. 

What I'd like to do is change the character's state directly. Any thoughts?

40 Replies
Math Notermans

After some more digging into Storyline's states-approach i discovered that SL handles states different depending on the amount of images used. As seen in a post above for only 2 images it creates 2 hidden Divs and toggles those. If it would handle it the same for multiple images, my approach above would work and then you could control any amount of states. But it handles multiple images differently. When more then 1 image there is only 1 SVG and the source image for that SVG is replaced by an png/jpg when switching states. Thus having multiple scenarios for states makes it really hard to script that consistently with Javascript.

Thus said i switched the approach, and found a well working solution.

Enter: the sprite and clippath approach...
In this sample below there is only 1 image... a large png, visible at start. When you click any of the state buttons the state is scripted. What happens is that with Javascripted the DIV gets masked with a clippath and then the complete DIV gets moved so it stays more or less in position. Ideally i would use background-position to move the image but it doesnot work ( yet )

stateLike

State like setup

For now this is a nice solution to get a state-like setup that you can script with Javascript.

As always i use JQuery and GSAP, all of this you can do in Vanilla Javascript, but i like the ease of use of GSAP for this.

Elaine Powell

That's awesome. Editing those states could be the answer to so many development problems.

So each state is a toggle? Like NormalState0 & NormalState1 for Normal?

When you can, please share your javascript code for these. I have an idea accessing these states might be a big deal.

I didn't read the whole comment thread. I've got your code.

Thank you so much!

Math Notermans

Hi Elaine, offcourse i will help you all the way. In fact jQuery is only a library that eases up selecting elements, and thus should not complicate things but make it easier :-) GSAP especially TweenLite , a older version of GSAP is used in Storyline itself for all animation. That said i gladly help you step by step. Either here in the forum or by direct mail or however...

Math Notermans

Luckily last month i figured out that Storyline as is uses TweenLite internally for all its animation. All transitions, animation and Motion Path animation you make in Storyline in fact is done behind the scenes with GSAP's TweenLite.

If you check some of my recent posts you will find samples of how to work with TweenLite in a standard Storyline. So i just tested this on my Sprite-state Storyline and as expected it really simplifies it. You can remove the WebObject and JQuery completely and with quite simple vanilla Javascript code trigger a Sprite animation as shown above in Storyline.

Steps to create a Sprite-animation in Storyline

Step 1 : Create your image
I found that easiest is having a long image with all steps/states next to eachother.
Ensure position of your steps in your image is ok. I use Photoshop to create it.
Online there are lot of tools to create a sprite too.
eg: https://www.piskelapp.com/ or https://www.toptal.com/developers/css/sprite-generator/
If you use Photoshop or Illustrator there are luckily also plugins to help create sprites.
https://www.formfcw.com/dev/spritecssgenerator/
I used this script to create a sprite from the PSD added.

sprites001

Resulting in this one sprite in which the images are perfectly positioned.

sprites2

Step 2 : Into Storyline
In Storyline after you imported the image, keeping the image at 100% makes it easier to script the position later on...

100percent

Now you need to be able to select it with Javascript. Easiest way is giving it a custom 'accessibility' name. You dont need to do this perse. You can select a image with its auto-generated acc-name, in this case 'mySprite.png', but i prefer changing that to ensure its unique.

accname

Step 3 : Javascript Fun
As said TweenLite is included and used in Storyline, so now its easy in fact.
Create an 'Execute Javascript' trigger that acts when the timeline starts.
Add the following code..
var StorylineSprite = document.querySelectorAll("[data-acc-text='someSprite']");
TweenLite.set(StorylineSprite, { transformOrigin: "left top", x:"0%" , clipPath:"inset(0% 90% 0% 0%)"});
As you can see in the image below, coding inside Storyline is no fun due to missing colorcoding. I use Sublime Text for all my Javascript. The colorcoding helps great to see errors and ensure your code is fine. I also always name the layer of an element in Storyline to its 'Accessibility Name' That way its easier to quickly see how you named elements. If you did...

jsCode

Depending on the amount of steps you have in your sprite. The 90% offcourse can vary. I have 10 images in my spritesheet to make it easy for myself.

Step 4 : Adding a button and state
Now we can add a button and animate the clippath. As you have to know how the values in the clippath animation influence your 'state-animation' its often easier to change the TweenLite setting a bit to see whats happening.

In the previous step we used TweenLite.set.. 'Set' immediately sets a value. So no time used..when triggered the clippath is set to the position asked for. 'TweenLite.to animates over time... so then you can see how your clippath is changing.

Some code like this will do the trick to see how the clippath moves...
var StorylineSprite = document.querySelectorAll("[data-acc-text='someSprite']");
TweenLite.to(StorylineSprite, 5,{ transformOrigin: "left top", x:"-240%" , clipPath:"inset(0% 80% 0% 10%)"});

First line should be clear now.
Selecting 'someSprite' with a vanilla selector
var StorylineSprite = document.querySelectorAll("[data-acc-text='someSprite']");

Then the TweenLite call
to: tweening to something (set or from are other possiblilities)

When using 'to', we need a duration...thats the 5... dont forget the comma behind it,cause all are arguments to pass to TweenLite.
StorylineSprite, 5,{ ... }

If you found the proper values for the clippath...then switch from 'to' to 'set' and you are done.
Alas is getting the proper x position with the Modern Player somewhat complex. Have some other posts dealing with that... If you have a case/course to work with we can figure that out together...

Michael Carlino

Hello Math, I am wondering could you do something like this to make a 3d object rotate.  I currently am using a slider to make it rotate which works fine, but you only get one rotation.  I would also like to try and use a mouse drag and not show the slider.  My thought original was trying to figure out a way to have the mouse drag change the state to the next picture. And then come up with if statements for when it hits the end to go back to the original state or if it comes back to the first state go to the last state.

Michael Carlino

Math, I hope you can give me a hand, I have tried to use GSAP dragable with you example but am not getting it to work.  I have just started with the create function so far, and was under the impression it should work withoutht haveing to enter the rest of the parameters.  I hope you can look at the storyline file and the below scripts and see if I am doing something wrong.

 

Script for weboject load

var loadedCount = 0;
var amountOfLibs = 3;
var player = GetPlayer();
var webObjectURL = "6FL3puY9jsM";

function loadJSfile(filename, filetype){if (filetype=="js"){ //if filename is a external JavaScript filevar fileref=document.createElement('script');
        fileref.setAttribute("type","text/javascript");
        fileref.setAttribute("src", filename);
        fileref.onload = function() {
            loadedCount++;
            console.log(loadedCount+" JS / "+filename+' loaded.');if(loadedCount >= amountOfLibs){
                 player.SetVar("javascriptsLoaded", true);
            }
        };
    }elseif (filetype=="css"){ //if filename is an external CSS filevar fileref=document.createElement("link")
        fileref.setAttribute("rel", "stylesheet")
        fileref.setAttribute("type", "text/css")
        fileref.setAttribute("href", filename);
        fileref.onload = function() {
            loadedCount++;
            console.log(loadedCount+" CSS / "+filename+' loaded.');if(loadedCount >= amountOfLibs){
                 player.SetVar("javascriptsLoaded", true);
            }
        };
    }if (typeof fileref!="undefined")
        document.getElementsByTagName("head")[0].appendChild(fileref)
}/*When we change anything in the scriptFolder this needs to change*/var scriptFolder ="story_content/WebObjects/"+webObjectURL+"/";/*And lastly we call the functions we want to run*/
loadJSfile(scriptFolder+"JavaScript/Draggable.min.js", "js");
loadJSfile(scriptFolder+"JavaScript/gsap.min.js", "js");



 

Script for the Draggable portion.

var StorylineSprite = document.querySelector("[data-acc-text='someSprite']");

Draggable.create(StorylineSprite);

Math Notermans

No problem Michael. A few errors in your code. You do have to register a GSAP plugin for it to work.
gsap.registerPlugin(Draggable);

And the loading part of your Javascript had some errors. 'elseif' needs to be 'else if'.. and some other errors i couldnot find, so i copied a script i had thats working.

Also it also works untill the second click on the element...not sure why... ill make a perfect sample soon.

Kind regards,
Math

Math Notermans

Figured out why the drag-drop behaviour was so weird. You create a Draggable not on a click of an element...but at start of the timeline. So best spot is when the javascript is loaded. Then it works fine. However still a lot to do to get a perfect drag-drop because you need to watch dragEnd and dropEnd... and all kind of different conditions. I am making a simple sample to show.

In this sample here i fixed the basics.