Motion ยท GSAP-style

Timelines

Choreography without callback pyramids. Steps run in order; '<' starts a step together with the previous one; '+=0.3' waits a beat first.

PositionMeaning
(none)Start after everything before it has finished
'<'Start at the same time as the previous step
'+=0.3'Start 0.3 s after the previous step finishes

Defaults passed to $.timeline({ ease, duration }) apply to every step. Timelines auto-play; pass paused: true and call await tl.play() when you want to control the start and know when it ended. .add(fn) runs any function as a step โ€” and if it returns a Promise, the timeline waits for it.

Live ยท Launch sequence

View the source for this demo
HTML
<div class="launch" aria-hidden="true">
  <div class="pad"></div>
  <div id="rk-flame"></div>
  <div id="rk"><span>๐Ÿš€</span></div>
  <div id="rk-count"></div>
  <div id="rk-msg">Liftoff โœ“</div>
</div>
<div class="row" style="margin-top:12px">
  <button type="button" id="rk-go">Launch</button>
  <button type="button" id="rk-reset">Reset</button>
</div>
<pre class="out" id="rk-log" aria-live="polite"></pre>
JavaScript
const log = s => $('#rk-log').text(($('#rk-log').text() + '\n' + s).trim());
const count = n => () => { $('#rk-count').text(n); log('T-' + n); };
const beat = { scale: 1, opacity: 1, duration: .55 };

$('#rk-go').on('click', async function () {
  $(this).prop('disabled', true);
  $('#rk-log').text('');
  const tl = $.timeline({ paused: true, ease: 'power2.out' })
    .add(count(3)).fromTo('#rk-count', { scale: 1.7, opacity: 0 }, beat)
    .add(count(2)).fromTo('#rk-count', { scale: 1.7, opacity: 0 }, beat)
    .add(count(1)).fromTo('#rk-count', { scale: 1.7, opacity: 0 }, beat)
    .to('#rk-count', { opacity: 0, duration: .15 })
    .to('#rk', { x: -4, duration: .05 }).to('#rk', { x: 4, duration: .05 }).to('#rk', { x: 0, duration: .05 })
    .to('#rk-flame', { opacity: 1, scaleY: 1.7, duration: .25 })
    .to('#rk', { y: -250, duration: 1.4, ease: 'power2.in' })
    .to('#rk-flame', { y: -250, duration: 1.4, ease: 'power2.in' }, '<')    // flame rides along
    .add(() => log('Liftoff โœ“'), '+=0.1')
    .fromTo('#rk-msg', { y: 14, opacity: 0 }, { y: 0, opacity: 1, duration: .4 });
  await tl.play();                                                          // resolves when every step is done
  $(this).prop('disabled', false);
});

$('#rk-reset').on('click', () => {
  $.to('#rk, #rk-flame', { x: 0, y: 0, scaleY: 1, duration: .3 });
  $('#rk-flame, #rk-msg').css('opacity', 0);
  $('#rk-count').text('');
  $('#rk-log').text('');
});

More examples

A hero intro

JavaScript
$.timeline({ duration: .5, ease: 'power2.out' })
  .from('.hero', { opacity: 0 })
  .from('.hero h1', { y: 30 })
  .from('.hero p', { y: 30 }, '<')        // together with the h1
  .from('.cta', { scale: .8 }, '+=0.2');  // a beat later

API

$.timeline({ paused?, onComplete?, ...defaults }) โ†’ timeline

Sequence tweens. Steps run in order; position '<' starts a step with the previous one; '+=0.3' waits 0.3 s first. Methods: .to(), .from(), .fromTo(), .add(fn) (waits if fn returns a Promise), .play() โ†’ Promise. Auto-plays unless paused: true.

Reference: $.timeline() โ†’

Migrating

Coming from GSAPSupports sequencing, '<', '+=N', defaults, paused, play() and onComplete. Not included: labels, absolute positions, '-=N' overlaps, pause()/reverse()/seek()/progress(), nesting and repeat.

Related features