JSFiddle - React, Tailwind, and code Playground

by HDL52

HTML

<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/ScrollTrigger.min.js"></script>
<section>
  <div class='bg'></div>
  <h1>Section 1</h1>
</section>

<section>
  <div class='bg'></div>
  <h1>Section 2</h1>
</section>

<section>
  <div class='bg'></div>
  <h1>Section 3</h1>
</section>

<section>
  <div class='bg'></div>
  <h1>Section 4</h1>
</section>

CSS

section {
  position: relative;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

h1 {
  color: #fff;
  text-shadow: 0px 1px, 1px 0px, 1px 1px;
  z-index: 1;
  font-size: clamp(2em, 12vw, 8em);
  font-weight: 800;
}

JavaScript

// https://codepen.io/GreenSock/pen/QWjjYEw

const img = [
	'https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgied0v0dgsgyqeoh7804b0ca54buj0qd.jpg',
  'https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imge39twvhffwemdsipqncbwko5vayrxgd.jpg',
  'https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgodr8vukcwco0h34p0ngc84462vs454j.jpg',
  'https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgj1udos782ybmzugwd1ltkozc9zxj3gz.jpg'
]

gsap.registerPlugin(ScrollTrigger);

let getRatio = el => window.innerHeight / (window.innerHeight + el.offsetHeight);

gsap.utils.toArray("section").forEach((section, i) => {
  section.bg = section.querySelector(".bg"); 

  section.bg.style.backgroundImage = `url(${img[i]})`;
  
  gsap.fromTo(section.bg, {
    backgroundPosition: () => i ? `50% ${-window.innerHeight * getRatio(section)}px` : "50% 0px"
  }, {
    backgroundPosition: () => `50% ${window.innerHeight * (1 - getRatio(section))}px`,
    ease: "none",
    scrollTrigger: {
      trigger: section,
      start: () => i ? "top bottom" : "top top", 
      end: "bottom top",
      scrub: true,
      invalidateOnRefresh: true // to make it responsive
    }
  });

});