Storyline360, Animation and GSAP ( Greensock )

Oct 23, 2020

Hi,

Trying to figure something out in SL360 i stumbled upon  the ds-bootstrap.min.js file thats added to the lib/scripts folder when publishing a Storyline360 file. In that file are GSAP(Greensock) references... some samples..
/*!
* VERSION: 1.11.8
* DATE: 2014-05-13
* UPDATES AND DOCS AT: http://www.greensock.com
*
* @license Copyright (c) 2008-2014, GreenSock. All rights reserved.
* This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for
* Club GreenSock members, the software agreement that was issued with your membership.
*
* @author: Jack Doyle, jack@greensock.com
*/

What i can find in that file that both GSAP CSS-plugin, easing and basic GSAP tweens are somehow used in Storyline.
Bad news however is that its a really old version they have implemented... from 2014... version 1.11.8, whereas the current GSAP version is version 3.5.1 !!!

Big advantages of the newer GSAP are mobile performance, speed and perfect control over SVG. So Storyline would certainly benefit from updating their internal used Storyline to the latest version.

As Storyline probably uses GSAP for all its animation features, the transitions and everything, i personally would really like it to have the control of GSAP in the hands of the frontend-user. I do use GSAP constantly in my elearning projects, but always have to add the newest version..

Is there an option to get the latest version of GSAP implemented in Storyline?
And can we have some better scripted control over Storyline elements...as you do use it already anyway?

Kind regards,
Math Notermans

 

63 Replies
Phil Mayor

@Jurgen I have an unrelated question, I want to copy code from a textbox into clipboard on the users machine.

My workaround at the moment is to pull the code from a text variable and then copy into the clipboard and that works, is there any way to use the contents of a textbox in storyline or am I chasing the impossible?

What I have works just wanted to make it an elegant solution.

Math Notermans

This gets all text in a given Storyline document.
const collection = document.getElementsByClassName("vector-text-item");

And then with gsap.getProperty you can get anything you want.... thus also the text.
let someText = gsap.getProperty(collection[0], "textContent");
console.log("someText : "+someText );

Ofcourse you can target any single textfield by using a more specific selector.

Math Notermans

As a reply on how to use DOM related plugins like GSAP's SplitText and ScrambleText on SVG text in Storyline i made a sample.

https://360.articulate.com/review/content/670bb432-b866-436b-8c1b-1b37d824dd0c/review

I use the lines of code as shown to Phil to grab the texts from all textfields in Storyline ( including buttons )...Determine which one i want to use ( in this case that is i1 ) Then i add a pure HTML text field to Storyline by appending it to a shape onscreen. And that textfield i can then target with GSAPs SplitText or ScrambleText.

Some weirdness with the missing i in textfield. Not sure where that comes from... but this is basically the setup you need to get this working.

Math Notermans

To help you get started....

This is my function that does the work.

function docLoaded_inSL(){

const collection = document.getElementsByClassName("vector-text-item");
const textsArray = [];
let baseStr;

for(var i = 0; i < collection.length;i++){
var sStr = player.GetVar("debugStr");
var someStr = gsap.getProperty(collection[i], "textContent");
var someNewStr = sStr+"<BR>"+"i "+i+": "+someStr;
player.SetVar("debugStr",someNewStr);
textsArray.push(gsap.getProperty(collection[i], "textContent"));
}

const h1 = document.createElement("H1");
const textNode = document.createTextNode(gsap.getProperty(collection[1], "textContent"));
h1.appendChild(textNode);
document.querySelector("[data-acc-text='canvasShape']").appendChild(h1);

var tl = gsap.timeline(),
mySplitText = new SplitText("H1", { type: "words,chars" }),
chars = mySplitText.chars; //an array of all the divs that wrap each character

gsap.set("H1", { perspective: 400 });


tl.from(chars, {
duration: 0.8,
opacity: 0,
scale: 0,
y: 80,
rotationX: 180,
transformOrigin: "0% 50% -50",
ease: "back",
stagger: 0.01
});

}

Basically it gets all texts from Storyline, and then i use one of them to create a HTML textfield to be targeted by SplitText.

Offcourse you can simplify it.

For example by creating a function to create HTML Text elements.

function createHTMLText(_string,_targetAccName,_elName){

const h1 = document.createElement(_elName);
const textNode = document.createTextNode(_string);
h1.appendChild(textNode);
document.querySelector("[data-acc-text='"+_targetAccName+"']").appendChild(h1);

}

You can then call that like this...


createHTMLText(gsap.getProperty(collection[1], "textContent"),"canvasShape","H1");

or if you donot want to use an existing Storyline textfield, just like this...

createHTMLText("Text to be animated by SplitText","myShape","H1");

And the SplitText functionality you also can put in a function.

function createSplitTextAni(_elName){

var tl = gsap.timeline(),
mySplitText = new SplitText(_elName, { type: "words,chars" }),
chars = mySplitText.chars; //an array of all the divs that wrap each character

gsap.set(_elName, { perspective: 400 });

tl.from(chars, {
duration: 0.8,
opacity: 0,
scale: 0,
y: 80,
rotationX: 180,
transformOrigin: "0% 50% -50",
ease: "back",
stagger: 0.01
});

}

So now this cleaned up code will work.

let mySplitString = "Bladibla vanillevla and some more text to test this";
createHTMLText(mySplitString,"accNameOfSomeShape","H1");
createSplitTextAni("H1");

function createHTMLText(_string,_targetAccName,_elName){

const newElement = document.createElement(_elName);
const textNode = document.createTextNode(_string);
newElement.appendChild(textNode);
document.querySelector("[data-acc-text='"+_targetAccName+"']").appendChild(newElement);

}

function createSplitTextAni(_elName){

var tl = gsap.timeline(),
mySplitText = new SplitText(_elName, { type: "words, chars" }),
chars = mySplitText.chars; //an array of all the divs that wrap each character

gsap.set(_elName, { perspective: 400 });

tl.from(chars, {
duration: 0.8,
opacity: 0,
scale: 0,
y: 80,
rotationX: 180,
transformOrigin: "0% 50% -50",
ease: "back",
stagger: 0.01
});

}

A few things to watch though. As i use H1 for texts... that is deliberately. You do need to format your HTML text somewhat, if you donot, you probably will not see a SplitText animation. Ofcourse you can format it any way you want, but it has to have some formatting before running SplitText on it.

Hope this helps.

Phil Mayor

Hey Math

Got lost on this

 var player = GetPlayer();
const collection = document.getElementsByClassName("vector-text-item");
const textsArray = [];
let baseStr;

for(var i = 0; i < collection.length;i++){
var sStr = player.GetVar("debugStr");
var someStr = gsap.getProperty(collection[i], "textContent");
var someNewStr = sStr+"<BR>"+"i "+i+": "+someStr;
player.SetVar("debugStr",someNewStr);
textsArray.push(gsap.getProperty(collection[i], "textContent"));
}

console.log("someText: "+ someNewStr);

That gives me all of the text in the course in the console, how do I get the text in just one shape?

I am missing something big I am sure.

Math Notermans

2 Phil Mayors here? Which one is the real one ;-) Or is one Phil Minor ;-)

Basically its all in the code as is.

Here i set a variable.
player.SetVar("debugStr",someNewStr);
So if you add an inline variable anywhere in your project...it will show the text.

But if you want a single value from a given element in your project...
Use this...

var player = GetPlayer();

// collection will be a collection of ALL elements with text
const collection = document.getElementsByClassName("vector-text-item");

/*
Depending of the amount of your texts you need a specific one.
Storyline will count up from 0 till the top layer on top.
So depending on your Storyline this number will differ.
0 is the lowest layer.
collection[0] or collection[7] or whatever layer you need.

*/

var someStr = gsap.getProperty(collection[0], "textContent");
player.SetVar("debugStr",someStr);
console.log("someText: "+ someStr);

Phil Mayor

Thanks Math there are two of me, the other one is the real one this is my other account for a specific company.

I was definitely missing something.

I need lots of these slides that are copies of one another with different text in, do the layers stay the same in each slide or do I need to find the correct layer on a slide by slide basis?

Sent from Outlook for iOS
This is a confidential email. Tesco may monitor and record all emails. The views expressed in this email are those of the sender and not Tesco. Tesco Stores Limited Company Number: 519500 Registered in England Registered Office: Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, AL7 1GA VAT Registration Number: GB 220 4302 31

Math Notermans

So this is the solution you want ;-)
https://360.articulate.com/review/content/07c6804a-d8cd-4d8a-add0-a8535d3bcd24/review

If you can add a acc-name to the textfields you want the text from... then this is the code to use.
var player = GetPlayer();
var myElement = document.querySelector("[data-acc-text='selectMe']");
var someStr = gsap.getProperty(myElement, "textContent");
player.SetVar("debugStr",someStr);
console.log("correct textfield is: "+someStr);

If however you cannot add an acc-name to that particular textfield... go for the solution on the last slide...'eightSlide'. I add a textfield with the text 'dummy' in it ( you can make it invisible ) and in the code check for that. Like this...
var player = GetPlayer();
const collection = document.getElementsByClassName("vector-text-item");


for(var i = 0; i < collection.length;i++){
    var someStr = gsap.getProperty(collection[i], "textContent");

    if(someStr=="dummy"){
console.log("textfield above the one we need is: "+i+" and text is "+someStr);
var layerNum = i-1;
    var correctStr = gsap.getProperty(collection[layerNum], "textContent");
            console.log("text is: "+correctStr);
    } else{
    }


For your ease added the .story

Math Notermans

Did check whether in some way you could use <BR> in the text but that didnot work.
However you can detect a specific character and add a br-element in the code when creating the HTML-text.

Showing here...
https://360.articulate.com/review/content/068eab8b-a19e-4852-96a2-5dca03bf9886/review

@Jurgen as you can see 'fi' has issues :-)

So how to do this ?

Split the text into an array.
//creating a textArray and splitting on %
const textArr = textStr1.split("%");

Then create a linebreak.
//creating a linebreak
const br = document.createElement("br");

Finally loop through your array and add an arrayentry ( so a line ) and then add a linebreak.
for(var j = 0; j < textArr.length;j++){
textNode = document.createTextNode(textArr[j]);
h1.appendChild(textNode);
h1.appendChild(br);
}

Working sample shared.

Math Notermans

Alas this still doesnot work.
Given this text: "Bladibla vanillevla end of a line %Some specific character on next line"

    let textStr1 = gsap.getProperty(collection[1], "textContent").replaceAll("\uE000", "f");
    console.log("t1: "+textStr1);
    let textStr2 = textStr1.replaceAll("\uE002", "fi");
    console.log("t2: "+textStr2);

The 'fi' in specific still fails..probably because it aint a ligature but just 2 characters...

Jürgen Schoenemeyer

your script

let textStr1 = gsap.getProperty(collection[1], "textContent").replaceAll("\uE000", "f");
console.log("t1: "+textStr1);

let textStr2 = textStr1.replaceAll("\uE002", "fi");
console.log("t2: "+textStr2);

is working for me

here is the complete script for multiple replace (for Open Sans, Calibri)

var player = GetPlayer();
let someStr = gsap.getProperty(collection[1], "textContent");

someStr = someStr.replaceAll("\uE000", "f");
someStr = someStr.replaceAll("\uE001", "ff");
someStr = someStr.replaceAll("\uE002", "fi");
someStr = someStr.replaceAll("\uE003", "fl");
someStr = someStr.replaceAll("\uE004", "ffi");
someStr = someStr.replaceAll("\uE005", "ffl");

player.SetVar("debugStr", someStr);
console.log("text[1]: "+ someStr);

Important: for Lato is another decoding (no ff, ffi, ffl)

var player = GetPlayer();
let someStr = gsap.getProperty(collection[1], "textContent");

someStr = someStr.replaceAll("\uE000", "f");
someStr = someStr.replaceAll("\uE001", "fi");
someStr = someStr.replaceAll("\uE002", "fl");

player.SetVar("debugStr", someStr);
console.log("text[1]: "+ someStr);

decoding is not necessary for these fonts (but it does not interfere)

  • Articulate
  • Consolas
  • Arial

storyline simply appends all available ligatures one after the other and numbers them

=> it must be tested individually for each font/variation -  e.g. Futura Bold has 19 ligatures, but Futura Medium only 2