Search code examples
javascripthtmlscrolltop

Image sequence starts animating before it's in view


I'm trying to replicate apple's image sequencing animation - https://www.apple.com/airpods-pro/. I've managed to track down a few examples and they all work great but they all trigger at the top of the page and I want mine to trigger in the middle of the page. I've tried attaching the trigger event to the #graphHolder container but it still starts cycling through the images as soon as you scroll at the top of the page. Can anyone point to where I'm going wrong? Here's a jsfiddle - https://jsfiddle.net/mvyw2bc3/2/

const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");

const frameCount = 132;
const currentFrame = index => ( `https://res.cloudinary.com/icebreakernz/image/upload/v1623273037/globalcampaign/plastic-free/sequence-test/IB-Graph_Animation${index.toString().padStart(4, '0')}.jpg`
)

const preloadImages = () => {
  for (let i = 1; i < frameCount; i++) {
    const img = new Image();
    img.src = currentFrame(i);
  }
};

const img = new Image()
img.src = currentFrame(1);
canvas.width=1920;
canvas.height=1080;
img.onload=function(){
  context.drawImage(img, 0, 0);
}

const updateImage = index => {
  img.src = currentFrame(index);
  context.drawImage(img, 0, 0);
}

window.addEventListener('scroll', () => {  
  const scrollTop = html.scrollTop;
  const maxScrollTop = html.scrollHeight - window.innerHeight;
  const scrollFraction = scrollTop / maxScrollTop;
  const frameIndex = Math.min(
    frameCount - 1,
    Math.ceil(scrollFraction * frameCount)
  );
  
  requestAnimationFrame(() => updateImage(frameIndex + 1))
});

preloadImages()

Solution

  • For anyone that runs in to the same problem, I ended up going with ScrollTrigger which made it super simple. Check out my fiddle here to see it working as it should - https://jsfiddle.net/jdrebugL/

    const canvas = document.getElementById("hero-lightpass");
    const context = canvas.getContext("2d");
    
    canvas.width = 1920;
    canvas.height = 1080;
    
    const frameCount = 132;
    const currentFrame = index => ( `https://res.cloudinary.com/icebreakernz/image/upload/v1623273037/globalcampaign/plastic-free/sequence-test/IB-Graph_Animation${(index + 1).toString().padStart(4, '0')}.jpg`
    );
    
    const images = []
    const airpods = {
      frame: 0
    };
    
    for (let i = 0; i < frameCount; i++) {
      const img = new Image();
      img.src = currentFrame(i);
      images.push(img);
    }
    
    gsap.to(airpods, {
      frame: frameCount - 1,
      snap: "frame",
      scrollTrigger: {
        scrub: 1,
        trigger: "#hero-lightpass",
        start: "top top",
        pin: true
      },
      onUpdate: render // use animation onUpdate instead of scrollTrigger's onUpdate
    });
    
    images[0].onload = render;
    
    function render() {
      context.clearRect(0, 0, canvas.width, canvas.height);
      context.drawImage(images[airpods.frame], 0, 0); 
    }