.boxes
  - const COUNT = 10
  - let b = 0
  while b < COUNT
    .box= b + 1
    - b++
View Compiled
*
  box-sizing border-box
  
body
  display grid
  place-items center
  min-height 100vh
  padding 0
  margin 0
  overflow-y hidden
  background hsl(0, 0%, 10%)
  
.boxes
  height 100vh
  width 100%
  overflow hidden
  position absolute
  
.box
  position absolute
  top 50%
  left 50%
  height 25vmin
  width 25vmin
  display grid
  place-items center
  font-size 5vmin
  font-family sans-serif
  transform translate(-50%, -50%)
  
  &:nth-of-type(odd)
    background hsl(90, 80%, 70%)
  
  &:nth-of-type(even)
    background hsl(90, 80%, 40%)
View Compiled
import gsap from 'https://cdn.skypack.dev/gsap'
import ScrollTrigger from 'https://cdn.skypack.dev/gsap/ScrollTrigger'

gsap.registerPlugin(ScrollTrigger)


const STAGGER = 0.2
const DURATION = 1
const OFFSET = 0
const BOXES = gsap.utils.toArray('.box')

const LOOP = gsap.timeline({
  paused: true,
  repeat: -1,
  ease: 'none'
})

const SHIFTS = [...BOXES, ...BOXES, ...BOXES]

SHIFTS.forEach((BOX, index) => {
  const BOX_TL = gsap
    .timeline()
    .fromTo(
      BOX,
      {
        xPercent: 100,
      },
      {
        xPercent: -200,
        duration: 1,
        ease: 'none',
        immediateRender: false,
      }, 0
    )
    .fromTo(
      BOX, {
        opacity: 0,
      }, {
        opacity: 1,
        duration: 0.25,
        repeat: 1,
        repeatDelay: 0.5,
        immediateRender: false,
        ease: 'none',
        yoyo: true,
      }, 0)
    .fromTo(
      BOX,
      {
        scale: 0,
      },
      {
        scale: 1,
        repeat: 1,
        zIndex: BOXES.length,
        yoyo: true,
        ease: 'none',
        duration: 0.5,
        immediateRender: false,
      },
      0
    )
  LOOP.add(BOX_TL, index * STAGGER)
})

const CYCLE_DURATION = STAGGER * BOXES.length
const START_TIME = CYCLE_DURATION + (DURATION * 0.5) + OFFSET
const END_TIME = START_TIME + CYCLE_DURATION

const LOOP_HEAD = gsap.fromTo(LOOP, {
  totalTime: START_TIME,
},
{
  totalTime: `+=${CYCLE_DURATION}`,
  duration: 1,
  ease: 'none',
  repeat: -1,
  paused: true,
})


const SCRUB = gsap.to(LOOP_HEAD, {
  totalTime: 0,
  paused: true,
  duration: 1,
  ease: 'none',
})


ScrollTrigger.create({
  start: 0,
  end: '+=2000',
  horizontal: false,
  pin: '.boxes',
  onUpdate: self => {
    SCRUB.vars.totalTime = LOOP_HEAD.duration() * self.progress
    SCRUB.invalidate().restart()
  }
})
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.