Scrolling Through Time: Creating Scroll-Driven Animations from Runway AI Video Sequences
The modern web is shifting away from static pages toward immersive, narrative-driven experiences. Among the most striking techniques capturing attention today is scroll-driven video animation—where a user’s scroll bar acts as a scrubbing tool, controlling the playback of a high-definition video frame by frame.
Thanks to generative AI video tools like Runway (specifically Runway Gen-2 and Gen-3), creating hyper-realistic, custom motion assets has never been easier. But how do you bridge the gap between a generative AI video file and a buttery-smooth, scroll-interactive web experience?
Let’s dive into how you can turn a Runway video into an engaging, high-performance frame sequence scroll-driven animation.
Why Frame Sequences Beat Standard Video Tags
You might wonder: Why not just use an HTML5 <video> tag with a JavaScript currentTime scroll listener?
While simpler in theory, video codecs (like MP4 or WebM) use interframe compression (temporal compression). They only store full image frames periodically (keyframes) and calculate the changes in between. When a user scrubs backwards and forwards rapidly via scrolling, standard video tags often stutter, lag, or display artifacts as the browser struggles to decode frames out of order.
A frame sequence—a series of individual, high-quality images (PNG or JPEG)—solves this entirely:
- Instant Scrubbing: Every frame is a standalone image, allowing for zero-latency, buttery-smooth scrubbing in either direction.
- Precise Control: You map exact scroll percentages to exact frame numbers.
- Creative Freedom: You can pause, reverse, or overlay DOM elements seamlessly at specific visual milestones.
Step 1: Generating Your Asset in Runway
Before writing a single line of code, you need a compelling visual sequence.
- Prompt for Motion: Open Runway and craft a prompt tailored for scroll storytelling. Scroll animations work best with continuous camera movement—like a forward dolly shot through a futuristic city, a product rotating 360 degrees, or a fabric billowing in slow motion.
- Optimize Duration: Generate a video that is clean and focused (typically 4 to 10 seconds is plenty for a scroll section).
- Export Cleanly: Download your video file in the highest possible resolution (1080p or 4K) with minimal compression.
Step 2: Extracting Frames
Next, you need to break your video down into its individual images. You can do this using professional software like Adobe Premiere or After Effects, or via command-line tools like FFmpeg, which is lightning-fast and free.
Run the following FFmpeg command in your terminal to extract frames at 30 frames per second (adjust fps to match your desired smoothness):
ffmpeg -i input_video.mp4 -vf "fps=30,scale=1920:-1:flags=lanczos" -q:v 2 frame_%04d.jpg
fps=30: Extracts 30 frames for every second of video.scale=1920:-1: Resizes the width to 1920px while maintaining the aspect ratio.-q:v 2: Ensures high image quality with a manageable file size.
Tip: Name your output files sequentially (frame_0001.jpg, frame_0002.jpg, etc.) so your script can easily loop through them.
Step 3: Bringing It to Life with HTML, CSS, and JavaScript
To implement the scroll effect, you will need a sticky container that stays pinned to the viewport while the user scrolls through a tall parent section. As the user scrolls, JavaScript calculates the current scroll progress and draws the corresponding frame onto an HTML5 <canvas>.
Here is a clean, self-contained implementation you can use as a starting point:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll-Driven Runway Animation</title>
<style>
body {
margin: 0;
background-color: #0b0b0b;
color: #ffffff;
font-family: system-ui, -apple-system, sans-serif;
}
/* The scroll container must be tall enough to allow scrolling */
.scroll-container {
height: 400vh;
position: relative;
}
/* Sticky wrapper keeps the visual locked to the screen while scrolling */
.sticky-wrapper {
position: sticky;
top: 0;
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
object-fit: cover;
}
.hero-text {
position: absolute;
z-index: 10;
text-align: center;
pointer-events: none;
text-shadow: 0 4px 12px rgba(0,0,0,0.6);
}
h1 { font-size: 3rem; margin: 0; }
p { font-size: 1.2rem; opacity: 0.8; }
</style>
</head>
<body>
<div class="scroll-container">
<div class="sticky-wrapper">
<div class="hero-text">
<h1>AI in Motion</h1>
<p>Scroll down to explore the sequence.</p>
</div>
<canvas id="hero-canvas"></canvas>
</div>
</div>
<script>
const canvas = document.getElementById('hero-canvas');
const context = canvas.getContext('2d');
// Configure frame settings
const frameCount = 120; // Total number of extracted frames
const currentFrame = index => `frames/frame_${String(index).padStart(4, '0')}.jpg`;
const images = [];
let framesLoaded = 0;
// Set canvas dimensions to match your source video aspect ratio
canvas.width = 1920;
canvas.height = 1080;
// Preload all images for zero-lag scrubbing
for (let i = 1; i <= frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
img.onload = () => {
framesLoaded++;
if (framesLoaded === 1) {
// Render the first frame immediately once ready
context.drawImage(img, 0, 0);
}
};
images.push(img);
}
// Update frame based on scroll position
window.addEventListener('scroll', () => {
const scrollContainer = document.querySelector('.scroll-container');
const scrollTop = window.scrollY;
const maxScrollTop = scrollContainer.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
// Map scroll fraction to frame index
const frameIndex = Math.min(
frameCount - 1,
Math.floor(scrollFraction * frameCount)
);
requestAnimationFrame(() => render(frameIndex));
});
function render(index) {
if (images[index] && images[index].complete) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(images[index], 0, 0);
}
}
</script>
</body>
</html>
Best Practices for Production Performance
While the technique is visually stunning, loading dozens—or even hundreds—of high-resolution images can strain a user’s bandwidth if not optimized properly. Keep these tips in mind before pushing to production:
- Optimize File Sizes: Compress your JPG frames (using tools like
mozjpegor ImageOptim) to strike a balance between visual fidelity and lightweight payloads. Often, a quality setting of 80% looks identical to the human eye while reducing file size by half. - Lazy Loading / Chunking: If your sequence is extremely long (
Leave a Reply