<label for="scrub">Web Animations API Timeline</label>
<input type="range" value="0" min="0" max="16000" step="1" id="scrub" />
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<p class="support">Requires native Web Animation Support (latest Firefox, Chrome, Opera)</p>
div {
  width: var(--side);
  height: var(--side);
  background: hsl(var(--hue, 323), 70%, 55%);
  margin: calc(var(--side) / 5) 0;
  -webkit-clip-path: var(--path, polygon(48% 0%, 52% 0%, 52% 100%, 48% 100%));
  clip-path: var(--path, polygon(48% 0%, 52% 0%, 52% 100%, 48% 100%));
  
  --side: 12.5vmin;
}

input[type="range"] {
  width: 100%;
  padding: 0;
  margin: 0;
}

div:nth-of-type(2) {
  --hue: 343;
  --path: polygon(50% 0%, 100% 100%, 0% 100%);
}
div:nth-of-type(3) {
  --hue: 363;
  --path: none;
}
div:nth-of-type(4) {
  --hue: 383;
  --path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
}
div:nth-of-type(5) {
  --hue: 403;
  --path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
}

p, label {
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 14px;
  padding: .5rem;
  text-align: center;
  display: block;
}

body {
  background: radial-gradient(circle, hsl(223, 90%, 16%), hsl(263, 90%, 6%));
  color: #fafcff;
}
* {
  box-sizing: border-box;
}

html:not(.unsupported) .support {
  display: none;
}
var divs = Array.from(document.querySelectorAll('div'));
var animations = [];
var isPlaying = true;
const DURATION = 8000;
var scrub = document.getElementById('scrub');

//Check for support... I'm including the polyfill at the moment so this won't actually be triggered
if (!document.body.animate) {
  document.documentElement.classList.add('unsupported')
}

//Animate each div... for simplicity, using delays and endDelays to make all animations have same start and end times (end time will be duration + delay + endDelay)
divs.forEach((div, i) => {
  var anim = div.animate({
    transform: ['translateX(0) rotate(0deg)', 'translateX(80vw) rotate(2700deg)']
  }, {
    duration: DURATION,
    easing: 'ease-in-out',
    fill: 'both',
    delay: DURATION / 4 * i,
    endDelay: DURATION - (DURATION / 4 * i)
  });
  
  animations.push(anim);
});

//Also start moving the "scrubber" control
adjustScrubber();

//Stop moving the "scrubber" when the animations finish
animations[0].onfinish = function() {
  isPlaying = false;
  console.log('finished');
}

//when the input range ("scrubber") is adjusted, pause the animations and change the `currentTime` property
scrub.addEventListener('input', e => {
  //console.log('input', e.currentTarget.value);
  var time = e.currentTarget.value;
  animations.forEach(animation => {
    animation.currentTime = time;
  });
  pauseAll();
});

//When the user finalizes the value for input range ("scrubber")... such as by letting go with the pointer, start playing the animations again. Unless the range is at the end (so the animation should finish). This set up does not work well with Right/Left arrows because of how often it fires the "change" event compared to with a mouse/touch. Plan to investigate a better approach to support keyboard and other inputs.
scrub.addEventListener('change', e => {
  console.log('change', e.currentTarget.value);
  if (animations[0].currentTime >= e.currentTarget.getAttribute('max')) {
    finishAll();
    return false;
  }
  requestAnimationFrame(adjustScrubber);
  playAll();
});



function adjustScrubber() {
  if (isPlaying) {
    scrub.value = animations[0].currentTime;
    requestAnimationFrame(adjustScrubber);
  }
}

function pauseAll() {
  isPlaying = false;
  animations.forEach(animation => {
    animation.pause();
  });
}
function finishAll() {
  isPlaying = false;
  animations.forEach(animation => {
    animation.finish();
  });
}
function playAll() {
  isPlaying = true;
  animations.forEach(animation => {
    animation.play();
  });
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/61811/web-animations-next-2.2.5.min.js