<h1 class="header-section">Scroll to see a timeline animation</h1>

<svg id="svg-stage" xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 600 1200">
  <path class="line01 line" d="M 10 200 600 200" ></path>
  <path class="line02 line" d="M 10 400 600 400" ></path>
  <path class="line03 line" d="M 10 600 600 600" ></path>
  <path class="line04 line" d="M 10 800 600 800" ></path>
  <path class="line05 line" d="M 10 1000 600 1000" ></path>
  <text class="text01" x="30" y="190">2018</text>
  <text class="text02" x="30" y="390">2019</text>
  <text class="text03" x="30" y="590">2020</text>

  <path class="theLine" 
        d="M -5,0
           Q 450 230 300 450 
           T 130 750
           Q 100 850 300 1000
           T 150 1200"
        fill="none" stroke-width="10px" />
  
  <circle class="ball ball01" r="20" cx="50" cy="100"></circle>
  <circle class="ball ball02" r="20" cx="278" cy="201"></circle>
  <circle class="ball ball03" r="20" cx="327" cy="401"></circle>
  <circle class="ball ball04" r="20" cx="203" cy="601"></circle>

</svg>
@font-face {
  font-display: block;
  font-family: Mori;
  font-style: normal;
  font-weight: 400;
  src: url(https://assets.codepen.io/16327/PPMori-Regular.woff) format("woff");
}

body {
  --light: #fffce1;
  width: 100%;
  height: 400vh;
  background: #0e100f;
  color: var(--light);
  font-family: "Mori", sans-serif;
}

.header-section {
  position: relative;
  text-align: center;
  margin: 100px auto 0;
}

#svg-stage {
  max-width: 600px;
  overflow: visible;
  margin-top: 60vh;
}

.ball {
  fill: var(--light);
  visibility: hidden;
}

.line {
  fill: none;
  stroke: var(--light);
  stroke-width: 2px;
}

text {
  fill: var(--light);
  font-size: 15px;
  visibility: hidden;
}

.theLine {
  stroke: var(--light);
}
console.clear();

gsap.registerPlugin(ScrollTrigger, DrawSVGPlugin, MotionPathPlugin);
gsap.defaults({ ease: "none" });

const pulses = gsap
  .timeline({
    defaults: {
      duration: 0.05,
      autoAlpha: 1,
      scale: 2,
      transformOrigin: "center",
      ease: "elastic(2.5, 1)"
    }
  })
  .to(".ball02, .text01", {}, 0.2)
  .to(".ball03, .text02", {}, 0.33)
  .to(".ball04, .text03", {}, 0.46);

const main = gsap
  .timeline({
    defaults: { duration: 1 },
    scrollTrigger: {
      trigger: "#svg-stage",
      scrub: true,
      start: "top center",
      end: "bottom center"
    }
  })
  .to(".ball01", { duration: 0.01, autoAlpha: 1 })
  .from(".theLine", { drawSVG: 0 }, 0)
  .to(
    ".ball01",
    {
      motionPath: {
        path: ".theLine",
        align: ".theLine",
        alignOrigin: [0.5, 0.5]
      }
    },
    0
  )
  .add(pulses, 0);

// 💚 This just adds the GSAP link to this pen, don't copy this bit
import { GSAPInfoBar } from "https://codepen.io/GreenSock/pen/vYqpyLg.js";
new GSAPInfoBar({ link: "https://gsap.com/docs/v3/Plugins/ScrollTrigger/" });
// 💚 Happy tweening!
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/16327/gsap-latest-beta.min.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/ScrollTrigger.min.js
  3. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/DrawSVGPlugin3.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/MotionPathPlugin.min.js