<h1>ScrollTrigger.batch()</h1>

<div class="container">
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
  <div class="box red"></div>
  <div class="box purple"></div>
  <div class="box orange"></div>
  <div class="box green"></div>
  <div class="box blue"></div>
  
</div>

body {
  text-align: center;
}

h1 {
  font-weight: 400;
  font-size: 2.5em;
}

.container {
  max-width: 800px;
  margin: auto;
  width: 100%;
  line-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}

.box {
  width: 31%;
  height: 15vh;
  margin: 5px;
  opacity: 0;
  will-change: transform;
}

header {
  position: fixed;
  top: 0px;
  left: 0px;
  padding: 6px 10px 6px 10px;
  border-bottom-right-radius: 26px;
  z-index: 100;
  background-color: rgba(0,0,0,0.5);
}
gsap.defaults({ease: "power3"});
gsap.set(".box", {y: 100});

ScrollTrigger.batch(".box", {
  //interval: 0.1, // time window (in seconds) for batching to occur. 
  //batchMax: 3,   // maximum batch size (targets)
  onEnter: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: {each: 0.15, grid: [1, 3]}, overwrite: true}),
  onLeave: batch => gsap.set(batch, {opacity: 0, y: -100, overwrite: true}),
  onEnterBack: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: 0.15, overwrite: true}),
  onLeaveBack: batch => gsap.set(batch, {opacity: 0, y: 100, overwrite: true})
  // you can also define things like start, end, etc.
});


// when ScrollTrigger does a refresh(), it maps all the positioning data which 
// factors in transforms, but in this example we're initially setting all the ".box"
// elements to a "y" of 100 solely for the animation in which would throw off the normal 
// positioning, so we use a "refreshInit" listener to reset the y temporarily. When we 
// return a gsap.set() in the listener, it'll automatically revert it after the refresh()!
ScrollTrigger.addEventListener("refreshInit", () => gsap.set(".box", {y: 0}));

External CSS

  1. https://codepen.io/GreenSock/pen/wvZQKQP.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.4.0/gsap.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.4.0/ScrollTrigger.min.js