FINDING YOUR DIRECTION

0%
← Scrollhaus Journal

AI Website Guides · 2026-10-05 · 7 min read

Image Sequence Scroll Animation With Canvas: Build a Scroll-Driven Hero Step by Step

An image sequence scroll animation draws pre-rendered frames to a canvas as the visitor scrolls, so scrolling plays a film forward and back with no seek lag. This tutorial walks through the full build, from FFmpeg frame extraction to canvas drawing, Lenis smoothing, mobile and accessibility, with working code.

Written by AmJ, founder of Scrollhaus, and edited by the Scrollhaus editorial team. Sources and methodology are documented in the article.

A frame from a ScrollHaus scroll-driven website drawn on a canvas
Scrollhaus Journal · AI Website Guides

Key takeaways

    Disclosure: ScrollHaus publishes this tutorial. The technique below is the same canvas image sequence approach our own scroll-driven websites use, described from public browser APIs (canvas, requestAnimationFrame, FFmpeg) so you can reproduce it in any stack.

    What is an image sequence scroll animation?

    An image sequence animation on scroll, also called a scroll sequence, plays a pre-rendered series of frames as the visitor scrolls. Scroll position decides which frame is drawn, so scrolling forward plays the film forward and scrolling back rewinds it. Because every frame is a still image drawn to an HTML5 canvas, playback is exact and smooth in every browser. This is the technique behind the product pages that appear to scrub a film as you scroll.

    Image sequence vs. video scrubbing

    Many tutorials call the result a scroll sequence or a video scroll animation. The visitor sees a video that plays on scroll, but under the hood it is an image sequence animation drawn page by page, which is why it stays smooth.

    You can set video.currentTime on scroll, but browsers decode video with keyframes, so seeking backward is slow and stutters. An image sequence has no seek cost: each frame is an independent image, decoded once and drawn instantly. The trade-off is weight, which the steps below manage with WebP and staged loading.

    Step 1: Extract frames from your video with FFmpeg

    Start from a clean source clip. Extract at a fixed frame rate (24 to 30 fps is plenty for scroll) and scale to the width you need. This command exports WebP frames at 1920 wide:

    ffmpeg -i hero.mp4 -vf "fps=24,scale=1920:-2" -c:v libwebp -quality 82 frames/f_%04d.webp

    A 6 second clip at 24 fps gives 144 frames. For mobile, export a second set at 960 wide and fewer frames (for example 12 fps) so phones download a fraction of the data.

    Step 2: Preload frames in stages

    Do not load every frame before showing anything. Load a coarse pass first (every 8th frame), draw the first frame immediately, then fill in the gaps. The animation is scrubbable in under a second and refines itself in the background.

    const frames = new Array(TOTAL);
    const load = (i) => new Promise((res) => {
      const img = new Image();
      img.decoding = 'async';
      img.onload = () => { frames[i] = img; res(); };
      img.src = `/frames/f_${String(i + 1).padStart(4, '0')}.webp`;
    });
    
    async function preload() {
      for (const step of [8, 4, 2, 1]) {
        const batch = [];
        for (let i = 0; i < TOTAL; i += step) if (!frames[i]) batch.push(load(i));
        await Promise.all(batch);
      }
    }

    Step 3: Set up the canvas for sharp, high-DPI rendering

    Size the canvas backing store by devicePixelRatio so it stays crisp on retina screens, and use a cover-fit calculation so the frame fills the viewport without distortion.

    const canvas = document.querySelector('#hero');
    const ctx = canvas.getContext('2d');
    
    function resize() {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = innerWidth * dpr;
      canvas.height = innerHeight * dpr;
      draw(current);
    }
    
    function drawCover(img) {
      const cw = canvas.width, ch = canvas.height;
      const s = Math.max(cw / img.naturalWidth, ch / img.naturalHeight);
      const w = img.naturalWidth * s, h = img.naturalHeight * s;
      ctx.drawImage(img, (cw - w) / 2, (ch - h) / 2, w, h);
    }

    Step 4: Map scroll position to a frame index

    Give the section a tall runway, typically two to three viewport heights, and pin the canvas with position: sticky (or fixed). Progress is the scrolled distance divided by the runway length, and the frame index is progress times the frame count.

    <section class="runway">
      <canvas id="hero"></canvas>
    </section>
    
    .runway { height: 300vh; position: relative; }
    #hero   { position: sticky; top: 0; width: 100vw; height: 100vh; display: block; }
    
    let current = 0;
    function onScroll() {
      const r = document.querySelector('.runway').getBoundingClientRect();
      const progress = Math.min(1, Math.max(0, -r.top / (r.height - innerHeight)));
      current = progress * (TOTAL - 1);
    }

    Step 5: Draw with requestAnimationFrame and cross-fade between frames

    Drawing inside requestAnimationFrame keeps work in sync with the display. Easing the displayed value toward the target gives inertia, and blending the two nearest frames removes any visible stepping when the frame count is low.

    let shown = 0;
    function tick() {
      shown += (current - shown) * 0.12;          // ease toward target
      const i = Math.floor(shown), t = shown - i;
      const a = frames[i], b = frames[Math.min(i + 1, TOTAL - 1)];
      if (a) {
        ctx.globalAlpha = 1; drawCover(a);
        if (b && t > 0.001) { ctx.globalAlpha = t; drawCover(b); }
      }
      requestAnimationFrame(tick);
    }
    addEventListener('scroll', onScroll, { passive: true });
    addEventListener('resize', resize);
    preload(); resize(); tick();

    Step 6: Smooth the scroll itself with Lenis

    Mouse wheels scroll in jumps. A library such as Lenis normalizes wheel input into eased scrolling so the film feels fluid. Initialize it once and call lenis.raf(time) from the same animation loop. It is optional, but it is the difference between a good and a premium feel.

    Scroll sequence in WordPress, Framer and no-code tools

    You do not have to hand-code a scroll sequence if you use a site builder. In WordPress, a scroll sequence plugin lets you upload the frames, set the section height and add text over the animation; create the sequence from your video, upload the image frames to the media library and the plugin handles preloading and scroll mapping. In Framer you can build a scroll-driven image animation with a code component that draws frames to a canvas, or with scroll-linked animations over a stack of images. The manual canvas method in this guide is the faster option when performance matters, because you control frame loading, mobile sets and the draw loop yourself, and it works on any site, plugin or not.

    How to add text overlays to a scroll animation

    Place normal HTML text above the sticky canvas and fade it in and out by progress ranges, for example headline at 0 to 25 percent and a second block at 40 to 65 percent. Because the text stays in the page, search engines and screen readers can read it.

    Common uses for scroll sequences

    Product reveals, hardware teardowns, ecommerce hero sections, portfolio intros, storytelling pages and cinematic landing pages all use this technique. The immersive experience gives visitors a reason to keep scrolling.

    Step 7: Mobile, memory and accessibility

    Troubleshooting: canvas not showing or not working

    Core Web Vitals and SEO

    Keep the largest contentful paint fast by showing a static poster frame immediately, then upgrading to the canvas. Reserve the canvas height with CSS to avoid layout shift, and keep real HTML headings on top. See Core Web Vitals for animated websites for the full checklist.

    Live examples and ready-made source

    Every ScrollHaus website is built on this exact pipeline, so you can scroll a working version and read how it behaves. For a wider overview of the category, see scroll animation websites, the animated hero section patterns and the guide to making an animated website with AI.

    Explore animated website templates at Scrollhaus →

    Need help with a template, account, or project? Email admin@scrollhaus.ai.