<main>
  <div id="control"></div>
  <div id="iterations"></div>
</main>
<p>This demo requires the Web Animations API and support for the <code>iterationComposite</code> property in the <code>Animation</code>'s effect options, such as Firefox Nightly 59+</p>
div {
  width: var(--size);
  height: var(--size);
  border: 2vmin solid hsl(var(--hue,43), 95%, 54%);
  border-radius: 30%;
  
  transform: rotate(0deg) translateX(0vmin) translateZ(0vmin);
  transform-origin: 50% 50%;
  position: absolute;
  opacity: 0;
}
#control {
  --hue: 183;
}

main {
  --size: 10vmin;
  
  width: var(--size);
  height: var(--size);
  position: relative;
  transform: translateZ(-400vmin);
  transform-style: preserve-3d;
}  
body {
  perspective: 10vmin;
  perspective-origin: 50% 50%;
}







/* Styles for explainer pieces: notes, body, button*/
body {
  min-height: 100vh;
  display: flex;
  justify-content: space-around;
  align-items: center;
  overflow: hidden;
  background: hsl(223, 40%, 12%);
  color: #fafdff;
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  perspective: 100vmax;
}
*, *::before, *::after {
  box-sizing: border-box;
}


body > p {
  position: absolute;
  z-index: 1;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 1rem;
  text-align: center;
  line-height: 1.35;
}
.supported body > p {
  display: none;
}
const iterationComposite = document.getElementById('iterations');
const control = document.getElementById('control');

const iterations = 400;
const duration = 20;
const angle = 5;
const tx = .125;
const tz = 1;
const opacity = 1 / iterations;

if (control.animate) {
  let animation = iterationComposite.animate([
    { 
      transform: `rotate(${angle}deg) translateX(${tx}vmin) translateZ(${tz}vmin)`,
      opacity: opacity,
      offset: 1
    }
  ], {
    duration: duration,
    direction: 'normal',
    iterations: iterations,
    easing: 'linear',
    fill: 'forwards',
    iterationComposite: 'accumulate'
  });
  
  animation.onfinish = () => {
    animation.reverse();
  };
  
  
  let animationControl = control.animate([
    {
      transform: `rotate(${iterations * angle}deg) translateX(${iterations * tx}vmin) translateZ(${iterations * tz}vmin)`,
      opacity: iterations * opacity,
      offset: 1
    }                                        
  ], {
    duration: duration * iterations,
    direction: 'reverse',
    iterations: 1,
    easing: 'linear',
    fill: 'both'
  });
  
  animationControl.onfinish = () => {
    animationControl.reverse();
  };

  if (animation.effect && animation.effect.iterationComposite === 'accumulate') {
    document.documentElement.classList.add('supported');
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.