scroll

Tags that turn the scrollbar into a timeline. Layers drift at their own depth, and a pinned stage plays a scene as you scroll through it. Scroll back up and everything runs in reverse.

Same scroll, different speeds.

<motion-parallax> moves each layer by its own depth, up, down or sideways

Show the codeHide the code
<script type="module">
  import "motion-components/motion-parallax";
</script>

<div class="depth-stage">
  <motion-parallax class="orb orb-yellow" depth="-2.2"></motion-parallax>
  <motion-parallax class="orb orb-blue" depth="1.4"></motion-parallax>
  <motion-parallax class="orb orb-ring" depth="3.2"></motion-parallax>
  <div class="lanes">
    <motion-parallax axis="x" depth="3" class="lane lane-near">
      <span>near  near  near  near  near  near</span>
    </motion-parallax>
    <motion-parallax axis="x" depth="-1.6" class="lane lane-mid">
      <span>middle  middle  middle  middle  middle  middle</span>
    </motion-parallax>
    <motion-parallax axis="x" depth="0.6" class="lane lane-far">
      <span>far away  far away  far away  far away  far away  far away</span>
    </motion-parallax>
  </div>
</div>

<style>
  .depth-stage { position: relative; height: 70vh; }
  .orb { position: absolute; display: block; border-radius: 50%; }
  .orb-yellow { width: 22vw; aspect-ratio: 1; top: 4%; right: 6%; background: #facc15; }
  .orb-blue { width: 14vw; aspect-ratio: 1; bottom: 6%; left: 4%; background: #60a5fa; }
  .orb-ring { width: 26vw; aspect-ratio: 1; top: 30%; left: 34%; border: 2px solid #2e2e3e; }
  .lanes {
    position: absolute; inset: 0; overflow: hidden;
    display: flex; flex-direction: column; justify-content: center; gap: 2vh;
  }
  .lane { display: block; white-space: pre; line-height: 1; margin-left: -30vw; }
  .lane-near { font-size: 14vw; font-weight: 800; }
  .lane-mid { font-size: 8vw; font-weight: 300; opacity: 0.8; }
  .lane-far { font-size: 4vw; font-weight: 600; color: #8888a8; }
</style>

Now keep scrolling. The stage stays put while the scene plays.

Scroll back up to take it apart.

<motion-scene> pins a stage and moves, scales and fades its children with the scroll

Show the codeHide the code
<script type="module">
  import "motion-components/motion-scene";
</script>

<motion-scene height="420vh" class="scene">
  <div class="ring ring-1" data-from='{"scale":0.2,"opacity":0}' data-to='{"scale":2.6,"opacity":0.9}' data-start="0" data-end="0.7"></div>
  <div class="ring ring-2" data-from='{"scale":0.1,"opacity":0}' data-to='{"scale":1.8,"opacity":0.8}' data-start="0.1" data-end="0.8"></div>
  <div class="ring ring-3" data-from='{"scale":0.05,"opacity":0}' data-to='{"scale":1.1,"opacity":1}' data-start="0.2" data-end="0.9"></div>

  <div class="word" data-from='{"scale":1,"opacity":1}' data-to='{"scale":9,"opacity":0}' data-start="0.62" data-end="0.92" aria-label="scroll">
    <span class="letter" aria-hidden="true" data-from='{"opacity":0,"x":"-38vw","y":"-30vh","rotate":"-40deg","scale":2.4}' data-to='{"opacity":1,"x":"0vw","y":"0vh","rotate":"0deg","scale":1}' data-start="0.04" data-end="0.36">s</span>
    <span class="letter" aria-hidden="true" data-from='{"opacity":0,"x":"24vw","y":"-42vh","rotate":"30deg","scale":0.4}' data-to='{"opacity":1,"x":"0vw","y":"0vh","rotate":"0deg","scale":1}' data-start="0.09" data-end="0.39999999999999997">c</span>
    <span class="letter" aria-hidden="true" data-from='{"opacity":0,"x":"-18vw","y":"36vh","rotate":"70deg","scale":1.8}' data-to='{"opacity":1,"x":"0vw","y":"0vh","rotate":"0deg","scale":1}' data-start="0.14" data-end="0.44">r</span>
    <span class="letter" aria-hidden="true" data-from='{"opacity":0,"x":"40vw","y":"28vh","rotate":"-25deg","scale":0.5}' data-to='{"opacity":1,"x":"0vw","y":"0vh","rotate":"0deg","scale":1}' data-start="0.19000000000000003" data-end="0.48">o</span>
    <span class="letter" aria-hidden="true" data-from='{"opacity":0,"x":"-44vw","y":"8vh","rotate":"15deg","scale":3}' data-to='{"opacity":1,"x":"0vw","y":"0vh","rotate":"0deg","scale":1}' data-start="0.24000000000000002" data-end="0.52">l</span>
    <span class="letter" aria-hidden="true" data-from='{"opacity":0,"x":"34vw","y":"-6vh","rotate":"-60deg","scale":0.3}' data-to='{"opacity":1,"x":"0vw","y":"0vh","rotate":"0deg","scale":1}' data-start="0.29" data-end="0.56">l</span>
  </div>

  <p class="scene-end" data-from='{"opacity":0,"y":"30px"}' data-to='{"opacity":1,"y":"0px"}' data-start="0.86" data-end="1">
    Scroll back up to take it apart.
  </p>
</motion-scene>

<style>
  .scene { display: block; position: relative; }
  .ring {
    position: absolute; top: 50%; left: 50%;
    width: min(70vw, 70vh); aspect-ratio: 1;
    margin: calc(min(70vw, 70vh) / -2) 0 0 calc(min(70vw, 70vh) / -2);
    border-radius: 50%; pointer-events: none;
  }
  .ring-1 { border: 2px solid #2e2e3e; }
  .ring-2 { border: 2px solid #60a5fa; }
  .ring-3 { border: 3px solid #facc15; }
  .word {
    position: relative; display: flex;
    font-size: 25vw; font-weight: 800; line-height: 1; color: #facc15;
  }
  .letter { display: inline-block; }
  .scene-end {
    position: absolute; left: 0; right: 0; bottom: 14vh; margin: 0;
    text-align: center; font-size: 2rem; font-weight: 600;
  }
</style>

Go deeper

Two longer pages take each tag further: a full parallax showcase with speed spectra and depth planes, and three pinned scenes built from data attributes.