Tools
Claude, GSAP ScrollTrigger, ffmpeg
Price
Free (no-charge GSAP license)
Time
30 min
Level
Intermediate

On a home page, this effect shows a product from several angles, a machine coming together or a before-and-after, without the visitor pressing play. It suits a craftsperson, a product brand or a software company showing its interface. The idea fits in three points:

  • the section holding the video is pinned (it stays on screen while the page scrolls);
  • the scroll position is turned into playback time, thanks to ScrollTrigger's scrub option;
  • the video is encoded so the browser can jump to any frame instantly.

1. Prepare the video

  1. Pick a short shotThree to eight seconds is enough, with no sound. The longer the video, the heavier the file.
  2. Encode one keyframe per frameA regular video only stores a full picture every now and then, which makes jumps jerky. ffmpeg's -g 1 option (install it with brew install ffmpeg) makes every frame self-contained. The file gets bigger, so scale it down to 1,280 pixels wide.
    Terminal
    ffmpeg -i my-video.mov -vf scale=1280:-2 -an -c:v libx264 -pix_fmt yuv420p -g 1 -movflags +faststart video.mp4

2. The prompt to give Claude

It names the library, the exact version and the cases Claude must handle (see the traps below).

Prompt
Write a standalone HTML page (one file, CSS and JS included) for the home page of [your business].
Structure: a full-screen intro with the title "[your title]", then a full-screen section with the video "video.mp4" (muted, playsinline, preload="auto"), then a following section.

Effect: when scrolling, the video section stays pinned and the video plays from 0 to its full duration in step with the scroll, over a distance of 3 screens. At about 70% of the way, the text "[your sentence]" fades in over the video.

Technical constraints:
- GSAP 3.15.0 and ScrollTrigger loaded from cdn.jsdelivr.net/npm, nothing else.
- ScrollTrigger with pin, start "top top", end "+=300%", scrub 0.5.
- Animate video.currentTime from 0 to video.duration with ease "none".
- Start the animation only once the video duration is known (readyState >= 1, otherwise the loadedmetadata event).
- If prefers-reduced-motion is on: no animation, a still frame of the video and the text visible.
- Readable layout on mobile (390 px wide), no horizontal scroll.
Comment the code simply.

3. The expected result, tested

Here is a complete file like the one you should get. It was tested in Chromium, on desktop and at mobile width: the video moves from 0 to 5 seconds as you scroll and the title appears at the end. Save it as index.html next to video.mp4 and open it with a double-click.

HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Scroll video</title>
<style>
  body { margin: 0; font-family: system-ui, sans-serif; background: #0b0b0c; color: #f5f5f7; }
  .intro, .suite { min-height: 100vh; display: grid; place-items: center; text-align: center; padding: 0 16px; }
  .scene { height: 100vh; position: relative; overflow: hidden; }
  .scene video { width: 100%; height: 100%; object-fit: cover; }
  .scene h2 { position: absolute; left: 0; right: 0; bottom: 10%; margin: 0; text-align: center; font-size: clamp(1.5rem, 5vw, 3.5rem); opacity: 0; }
  @media (prefers-reduced-motion: reduce) { .scene h2 { opacity: 1; } }
</style>
</head>
<body>
  <section class="intro"><h1>Scroll down</h1></section>
  <section class="scene">
    <video id="film" src="video.mp4" muted playsinline preload="auto"></video>
    <h2 id="title">Your product, from every angle</h2>
  </section>
  <section class="suite"><p>The rest of the page</p></section>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js"></script>
  <script>
    gsap.registerPlugin(ScrollTrigger);
    const video = document.getElementById("film");
    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    function start() {
      if (reduced) { video.currentTime = video.duration / 2; return; }
      const tl = gsap.timeline({
        scrollTrigger: { trigger: ".scene", start: "top top", end: "+=300%", scrub: 0.5, pin: true }
      });
      tl.to(video, { currentTime: video.duration, ease: "none", duration: 1 }, 0)
        .to("#title", { opacity: 1, duration: 0.2 }, 0.7);
    }
    if (video.readyState >= 1) start();
    else video.addEventListener("loadedmetadata", start, { once: true });
  </script>
</body>
</html>

Traps to avoid

  • A jerky video. If it is not encoded with -g 1, the browser has to rebuild each frame from the last full picture, and the effect stutters.
  • An unknown duration. If the script starts before the browser knows how long the video is, the animation has no end point. Hence the readyState condition in the prompt.
  • Testing with a server that is too basic. To jump inside a video, the browser asks for pieces of the file ("range requests"). The small python3 -m http.server does not handle them and the video stays stuck on its first frame. Open the file directly, or test on your real host.

Good to know

  • On some phones, jumping around in a video stays less smooth than on desktop. The other method, heavier to prepare, exports the video as a sequence of images drawn into a canvas: ask Claude for it if the mobile result is not good enough.
  • A video encoded with -g 1 weighs more. Keep it short and check the load time on a mobile connection.
  • Think of visitors who turn off animations: the prompt handles the system's prefers-reduced-motion setting.
  • GSAP and its plugins are free, including for a commercial website, according to their license page.

Check before going live. Read the code Claude gives you before publishing it, load scripts only from a well-known CDN and pin the version (here 3.15.0) so an update cannot break your page. Only use videos you have the rights to.

Sources

Want a home page that really shows off your product? That is what I build in my websites.