Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <link href='https://fonts.googleapis.com/css?family=Asap:400,700' rel='stylesheet' type='text/css'>
<div id="stage">
  <div class="container">
    <div class="all-words">
      <div class="split quote" id="quote"></div>
    </div>
  </div>
</div>

<div class="form-stuff">
  <div class="buttons">
    <button id="play">REPLAY</button>
    <button id="get-tl">PLAY FROM DATA</button>
  </div>
  <h4>YOUR MESSAGE</h4>
  <textarea name="content" id="message" cols="50" rows="2">Test one two</textarea>
</div>
<div class="data-holder">
  <textarea name="tl-data" id="tl-data" cols="100" rows="100"></textarea>
</div>
              
            
!

CSS

              
                body{
  font-family: 'Asap', Arial, Helvetica, sans-serif;
  color:white;
  margin: 0;
}

/* styles for elements addressed in the animation code */
#stage {
  width: 600px;
  display: block;
  height: 240px;
  position: relative;
  margin: 0;
  overflow: hidden;
  background:black url(https://s.cdpn.io/16327/texture_bg.jpg) no-repeat center top;
}
#tl-data {
  font-family: courier, 'courier new';
  font-size: 10px;
}

.container {
  position: relative;
  overflow: visible;
  display: flex;
  flex-flow: row nowrap;
  justify-content: center;
  align-items: center;
  width: 600px;
  height: 120px;
  text-rendering: auto;
  box-sizing: border-box;
  margin: 50px auto 0;
}
.all-words {
  position: relative;
  overflow: visible;
}
.split{
  -webkit-transform: translate3d(0, 0, 0);
  font-size:24px;
  line-height:1.0em;
  color:#dedede;
  margin:10px;
  visibility:hidden;
  text-align: center;
}

.split div{
  border-radius: 0;
  border: 2px solid green;
  background:#ffffff;
  padding:0.25em 0.5em;
  margin:4px 0;
  -webkit-font-smoothing: antialiased;
	-moz-font-smoothing:antialiased;
  color:green;
  font-weight:bold; 
}

/* utility styles - not needed for animation */
.dashes {
  border: 1px #ffffff dashed;
}

.form-stuff h4 {
  color: #333333;
  font-size: 20px;
  margin: 10px 0 4px;
}

button {
  display: inline-block;
  background: green;
  border: none;
  border-radius: 5px;
  padding: 7px 15px 8px;
  color: white;
  font-size: 18px;
  margin: 5px 5px 5px 0;
}


              
            
!

JS

              
                /*
See https://www.greensock.com/splittext/ for details. 
This uses SplitText & CustomEase which are membership benefits of Club GreenSock, https://www.greensock.com/club/
*/

CustomEase.create("magneticOut","M0,0 C0,0 0.039,-0.004 0.06,-0.013 0.085,-0.023 0.104,-0.033 0.125,-0.052 0.217,-0.136 0.27,-0.202 0.365,-0.289 0.398,-0.32 0.423,-0.337 0.46,-0.359 0.477,-0.369 0.493,-0.374 0.512,-0.377 0.528,-0.38 0.541,-0.38 0.557,-0.376 0.574,-0.373 0.587,-0.367 0.602,-0.358 0.617,-0.349 0.627,-0.34 0.64,-0.327 0.66,-0.305 0.673,-0.288 0.69,-0.261 0.711,-0.226 0.725,-0.203 0.74,-0.164 0.778,-0.063 0.801,-0.002 0.83,0.103 0.869,0.248 0.886,0.331 0.915,0.481 0.953,0.684 1,1 1,1");

var quote = document.getElementById("quote"),
    quoteSplit,
    allWords,
    stageHt = 240,
    stageWd = 600,
    fontSize = 24,
    messageDisplayTime = 1,
    tlData = null,
    container = $('.container'),
    cBCR = container[0].getBoundingClientRect(),
    messagePadding = 75,
    tl = new TimelineMax();

function populateWords(buildAll) {
  buildAll = buildAll === undefined ? true : buildAll;
  quote.innerHTML = $('textarea#message').val();
  quoteSplit = splitQuote();
  quoteSplit.words.forEach(function(el,index){
    el.setAttribute('id',`word-${index+1}`);
    el.className = 'word';
  })
  allWords = quoteSplit.words;
  if( buildAll ) {
    setIn();
  }
}

function splitQuote() {
  return new SplitText(quote, {type:"words"});
}

function setIn() {
  tl.set(quote, {visibility:"visible", data:{tweenType: 'set',params:{visibility: "visible"}}});
  tl.set(allWords, {boxShadow:"(0px 0px 6px rgba(0, 0, 0, 0.8)", data:{tweenType:'set',params:{boxShadow:"(0px 0px 6px rgba(0, 0, 0, 0.8)"}}});

  //intro
  for(var i = 0; i < allWords.length; i++) {

    var randDur = 0.4 + Math.random() * 0.5;
    var randRot = _.random(-60, 60);
    var fromX = _.random((stageWd/2)*-1, stageWd/2);
    var fromY = _.random((stageHt/2)*-1, stageHt/2);
    var toX = _.random((cBCR.width/2-messagePadding)*-1, (cBCR.width/2)-messagePadding);
    var toY = _.random( fontSize, messagePadding );
    var randAddTime = Math.random() * 0.5;

    tl.fromTo(allWords[i], randDur,
      {x:fromX,y:fromY,autoAlpha:0},
      {force3D:true,rotation:randRot,y:toY,x:toX,autoAlpha:1,ease:'Back.easeIn',
        data:{
          tweenType:'fromTo',
          params:{
            from:{
              x:fromX,
              y:fromY,
              autoAlpha:0
            },
            to:{
              force3D:true,
              rotation:randRot,
              y:toY,
              x:toX,
              autoAlpha:1,
              ease:'Back.easeIn'
            }
          }
        }
      },
      randAddTime);
  }
  doMessage();
}

function doMessage() {
  //show sentence
  tl.addLabel('sentence');
  let labelTime = tl.getLabelTime('sentence');
  for(i = 0; i < quoteSplit.words.length; i++) {
    var randRot = _.random(-6, 10);
    var randX = _.random(-2, 2);
    var randY = _.random(-4, +4);
    tl.to(quoteSplit.words[i], 0.5, {
      rotation:randRot,
      x:randX,
      y:randY,
      data:{
        tweenType:'to',
        params:{
          rotation:randRot,
          x:randX,
          y:randY
        }
      }
    }, "sentence+=" + i * 0.3);
  }
  setOut();
}

function setOut() {
  //outro
  // staggerTo will return a timeline - need to later reconstruct the stagger - see animateFromDb()
  tl.staggerTo(quoteSplit.words, 0.4, {autoAlpha:0, scale:0, ease:"magneticOut",
    data:{tweenType: 'staggerTo', staggerTime: 0.2, staggerPosition: `+=${messageDisplayTime}`, staggerDuration: 0.3, params:{autoAlpha:0, scale:0, ease:"magneticOut"}}},
    0.01, `+=${messageDisplayTime}`);
}

// parse the timeline data
function getGsapData(target, timelineData) {
  
  var children = target.getChildren(false, true, true);
  children.forEach(child => {
    if (child instanceof TweenLite) {
      if(Array.isArray(child.target)) {
        var targetSelectors = [];
        for( let targ of child.target ) {
          var sel = buildSelector(targ);
          targetSelectors.push(sel);
        }

        timelineData.push({
          type: 'tween',
          tweenType: child.data.tweenType,
          isArray: true,
          targetSelectors: targetSelectors,
          selector: child.target.selector, // might be undefined
          duration: child.duration(),
          gsTrans: child.target._gsTransform,
          startTime: child.startTime(),
          vars: _.cloneDeep(child.vars)
        });

      } else {

        var concatSelector = buildSelector(child.target);
        timelineData.push({
          type: 'tween',
          tweenType: typeof(child.target) !== "function" ? child.data.tweenType : "function",
          isArray: false,
          selector: typeof(child.target) !== "function" ? child.target.selector : child.target.name,
          concatSelector: concatSelector,
          duration: child.duration(),
          gsTrans: typeof(child.target) !== "function" ? child.target._gsTransform : null,
          startTime: child.startTime(),
          delay: child.delay(),
          vars: _.cloneDeep(child.vars)
        });
      }
    } else {

      if( (child instanceof TimelineMax) ) {
        var labels = child.getLabelsArray(); // each object in the array will contain {name,time}
        timelineData.push({
          type: 'timeline',
          labels: labels,
          tlID: child.data.id,
          startTime: child.startTime(),
          delay: child.delay(),
          vars: _.cloneDeep(child.vars),
          children: getGsapData(child, []),
        });
      } else if(child instanceof TimelineLite ){
        // must be an instance of TimelineLite
        timelineData.push({
          type: 'timeline',
          labels: [],
          tlID: null,
          startTime: child.startTime(),
          delay: child.delay(),
          vars: _.cloneDeep(child.vars),
          children: getGsapData(child,  []),
        });
      } else {
        timelineData.push({
          type: 'timeline',
          labels: [],
          tlID: child.tlID,
          startTime: child.startTime,
          delay: child.delay(),
          vars: _.cloneDeep(child.vars),
          children: getGsapData(child,  []),
        });
      }
    }
  });

  return timelineData;
}

// reapply the animation from the parsed data
function animateFromData(storedData) {

  var tl = new TimelineMax();
  
  storedData.forEach(function(child){ // can also pass twIndex as 2nd arg
    var target,
        type,
        startTime = child.startTime;

    if(child.type == 'timeline') {
      // must be a staggerFrom, staggerTo or other animation with a timeline wrapper.
      // get the child data
      var stData = child.children[0].vars.data;
      type = stData.tweenType;
      var staggerTime = stData.staggerTime;
      var stDur = stData.staggerDuration === undefined ? child.children[0].duration : stData.staggerDuration;
      var selectors = [];
      var delays = [];
      child.children.forEach(function(staggerChild){ // could also pass an index like i
        let sel = staggerChild.concatSelector;
        selectors.push(sel);
      });

      let staggerPosition = stData.staggerPosition === undefined ? startTime : stData.staggerPosition;
      switch(type) {
        case 'staggerTo':
          tl.staggerTo(selectors,stDur,stData.params,staggerTime,staggerPosition);
        break;
        case 'staggerFrom':
          tl.staggerFrom(selectors,stDur,stData.params,staggerTime,staggerPosition);
        break;
        case 'staggerFromTo':
          // reconnect selectors
          var connected = [];
          for( let slctr of selectors ) {
            connected.push(document.querySelector(slctr));
          }
          tl.staggerFromTo(connected,stDur,stData.params.from,stData.params.to,staggerTime,staggerPosition);
        break;
      }
    } else {
      // must be an actual tween
      type = child.tweenType;
      if( type == "function" ) {
        child.vars.data = {params: null};
      }
      var duration = child.duration,
          vars = child.vars,
          params = vars.data.params;

      if(child.isArray) {
        target = [];
        child.targetSelectors.forEach( function(sel,selIndex) {
          target.push(sel);
        });
      } else {
        target = child.concatSelector;
      }

      switch(type) {
        case 'set':
          tl.set(target,params,startTime);
        break;
        case 'to':
          tl.to(target,duration,params,startTime);
        break;
        case 'from':
          tl.from(target,duration,params,startTime);
        break;
        case 'fromTo':
          tl.fromTo(target,duration,params.from,params.to,startTime);
        break;
      }
    }
  });
  return tl;
}

/*******************************************************/
// utility stuff and trigger initial animation on load
$('#play').on("click", function(){
  tl.seek(0).clear();
  populateWords();
});

$('#get-tl').on("click", function(){
  quote.innerHTML = "";
  populateWords(false);
  let tlData = getGsapData(tl, []);
  $('.data-holder #tl-data').val(JSON.stringify(tlData,null,2));
  tl.kill();
  tl = animateFromData(tlData);
  tl.play();
});

// just a convenience method to assure the selector/id is correct when reassembling an animation
function buildSelector(target) {
  var attrs;
  if( typeof(target) == "function" ) {
    attrs = {type: "function", name: target.name };
  } else if( target.attributes !== undefined ) {
    attrs = target.attributes;
  } else if( target.target !== undefined ) {
    attrs = target.target.attributes !== undefined ? target.target.attributes : attrs;
  } else if( target[0].attributes !== undefined ) {
    attrs = target[0].attributes;
  } else if( target.target.attributes !== undefined ) {
    attrs = target.target.attributes;
  }

  var sel = '',
      id = null,
      cl = null; // class is a reserved word

  // check to see what's in there. Each should contain id, class, style
  if(attrs.id !== undefined ) {
    id = attrs.id.value !== undefined ? attrs.id.value : id;
  }

  if( attrs.class !== undefined ) {
    cl = attrs.class.value !== undefined ? attrs.class.value : cl;
  }
  
  sel = id !== null ? `.container .all-words #quote #${id}` : sel;
  var clArr = [];
  clArr = cl !== null ? cl.split(' ') : clArr;
  if(clArr.length > 0) {
    for ( let C of clArr ) {
      sel += `.${C}`;
    }
  }

  return sel;
}

function randomInteger(min, max){
	return Math.floor(Math.random() * (1 + max - min) + min);
}

populateWords();
              
            
!
999px

Console