scroll-video
Skill2 starsChanged 6 months agoMITView on GitHub
Create scroll-driven video playback for landing pages and websites. The video plays forward as the user scrolls down and reverses when scrolling up — like Apple product pages and drinksom.eu. Trigger on: "סרטון בגלילה", "scroll video", "scroll-driven video", "video on scroll", "סרטון שרץ בגלילה", "video scrubbing", "scroll playback", "video parallax scroll", "apple style video scroll", "play video on scroll down".
- Installs packages
1 other project uses a version of this.
Adopted by
Most recently taken up by
- ne11nn/cantos-plugintheir own version, first seen Oct 20, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- ne11nn/cantos-plugin★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- app-foundationsSkill
- brand-guidelinesSkill
- code-reviewerSkill
- content-templatesSkill
- design-md-librarySkill
- hebrew-business-docsSkill
- image-genSkill
- logo-creatorSkill
- marketing-copywriterSkill
- premium-landing-effectsSkill
- production-security-auditSkill
- seo-launchpadSkill
- skill-creatorSkill
- ui-ux-pro-maxSkill
- visual-generatorSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 13
Create buttery-smooth scroll-controlled video playback using Lenis + GSAP ScrollTrigger + Canvas.
- Very specific: line 66
Place hero-video.mp4 in the same directory as the landing page HTML.
- Very specific: line 311
Video doesn't play at all: Check canplaythrough event fires.
- Very specific: line 316
Headline doesn't fade: Verify hero-headline ID exists and section height is tall enough.
- Very specific: line 318
Content below hero invisible: Check data-reveal scroll triggers aren't hiding elements that should be visible.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Scroll-Driven Video
Create buttery-smooth scroll-controlled video playback using Lenis + GSAP ScrollTrigger + Canvas.
How it works
The video plays forward as the user scrolls down and reverses when scrolling up. The hero section stays pinned while the video progresses. A headline overlay fades out with parallax as you scroll.
Architecture:
- Hidden
<video>element (muted, no autoplay) — serves as the frame source <canvas>draws the current video frame on every rAF — smoother than native video display- GSAP ScrollTrigger maps scroll progress (0→1) to a proxy object
- A render loop uses lerp interpolation to smoothly approach the target time
- Lenis provides smooth scroll behavior connected to ScrollTrigger
Critical: Video encoding
This is the #1 factor for smoothness. The video MUST be re-encoded with a keyframe on every single frame. Without this, the browser can only seek to sparse keyframes, causing visible jumps.
ffmpeg command
ffmpeg -i INPUT.mp4 -c:v libx264 -preset slow -crf 22 -g 1 -keyint_min 1 -an -movflags +faststart OUTPUT.mp4Flags explained:
-g 1 -keyint_min 1— keyframe on EVERY frame (the secret sauce)-an— strip audio (not needed for scroll video)-movflags +faststart— move moov atom to start for instant web playback-crf 22— good quality without bloating file size-preset slow— better compression (file will be ~13MB for 8s video)
If ffmpeg is not installed
winget install Gyan.FFmpeg # Windows
brew install ffmpeg # macOSStep-by-step implementation
Step 1: Copy and re-encode the video
ffmpeg -i original-video.mp4 -c:v libx264 -preset slow -crf 22 -g 1 -keyint_min 1 -an -movflags +faststart hero-video.mp4Place hero-video.mp4 in the same directory as the landing page HTML.
Step 2: HTML structure
<!-- Hero section — height creates scroll runway for video -->
<section id="hero-video-section" class="relative h-[500vh]" style="background:#0d0d0d;">
<div class="sticky top-0 h-screen w-full overflow-hidden">
<!-- Hidden video source -->
<video id="hero-video" muted playsinline preload="auto" src="hero-video.mp4"
style="position:absolute;width:0;height:0;opacity:0;pointer-events:none;"></video>
<!-- Canvas renders video frames smoothly -->
<canvas id="hero-canvas" style="position:absolute;inset:0;width:100%;height:100%;"></canvas>
<!-- Dark gradient overlay -->
<div class="absolute inset-0 pointer-events-none"
style="background:linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.1) 40%, rgba(0,0,0,0.25) 100%);z-index:1;"></div>
<!-- Headline overlay -->
<div id="hero-headline" class="absolute inset-0 flex items-center" style="z-index:2;">
<div class="max-w-7xl mx-auto px-6 w-full">
<div class="md:max-w-2xl text-right">
<h1 class="text-6xl md:text-8xl font-black text-white leading-[0.95] mb-8 tracking-tight">
כותרת ראשית
</h1>
<p class="text-lg md:text-xl text-white/60 mb-12 max-w-md leading-relaxed font-light">
תיאור משני
</p>
<div class="flex flex-wrap gap-4">
<button class="bg-red-700 text-white font-bold px-10 py-4 rounded-full text-base hover:bg-red-600 transition-all duration-200">
CTA ראשי
</button>
</div>
</div>
</div>
</div>
</div>
</section>Step 3: CDN scripts (add before closing </body>)
<script src="https://unpkg.com/lenis@1.1.18/dist/lenis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script>gsap.registerPlugin(ScrollTrigger);</script>
<script src="scroll-video.js"></script>Step 4: JavaScript — scroll-video.js
(function () {
'use strict';
/* ── Lenis smooth scroll ── */
var lenis = new Lenis({
duration: 1.6,
easing: function (t) { return Math.min(1, 1.001 - Math.pow(2, -10 * t)); },
smoothWheel: true
});
function raf(time) { lenis.raf(time); requestAnimationFrame(raf); }
requestAnimationFrame(raf);
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add(function (time) { lenis.raf(time * 1000); });
gsap.ticker.lagSmoothing(0);
/* ── Grain overlay (optional — cinematic texture) ── */
var grain = document.createElement('div');
grain.id = 'grain-overlay';
grain.style.cssText = [
'position:fixed;inset:0;z-index:9998;pointer-events:none',
'opacity:0.04',
'background-image:url("data:image/svg+xml,%3Csvg viewBox=\'0 0 256 256\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'noise\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.9\' numOctaves=\'4\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23noise)\'/%3E%3C/svg%3E")',
'background-repeat:repeat',
'mix-blend-mode:overlay'
].join(';');
document.body.appendChild(grain);
setInterval(function () {
grain.style.transform = 'translate(' + (Math.random() * -100) + 'px,' + (Math.random() * -100) + 'px)';
}, 80);
/* ── Scroll-driven video with canvas + lerp ── */
var heroSection = document.getElementById('hero-video-section');
var heroVideo = document.getElementById('hero-video');
var heroCanvas = document.getElementById('hero-canvas');
var heroHeadline = document.getElementById('hero-headline');
if (heroVideo && heroCanvas && heroSection) {
var ctx = heroCanvas.getContext('2d');
var targetTime = 0;
var currentTime = 0;
var videoReady = false;
var lerpFactor = 0.06; // Lower = smoother but slower to catch up
function resizeCanvas() {
var dpr = window.devicePixelRatio || 1;
heroCanvas.width = window.innerWidth * dpr;
heroCanvas.height = window.innerHeight * dpr;
heroCanvas.style.width = window.innerWidth + 'px';
heroCanvas.style.height = window.innerHeight + 'px';
ctx.scale(dpr, dpr);
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
function drawFrame() {
if (!videoReady) return;
var cw = window.innerWidth;
var ch = window.innerHeight;
var vw = heroVideo.videoWidth;
var vh = heroVideo.videoHeight;
if (!vw || !vh) return;
var scale = Math.max(cw / vw, ch / vh);
var dw = vw * scale;
var dh = vh * scale;
var dx = (cw - dw) / 2;
var dy = (ch - dh) / 2;
ctx.clearRect(0, 0, cw, ch);
ctx.drawImage(heroVideo, dx, dy, dw, dh);
}
/* Render loop — lerp currentTime toward target */
function renderLoop() {
if (videoReady) {
var diff = targetTime - currentTime;
if (Math.abs(diff) > 0.001) {
currentTime += diff * lerpFactor;
heroVideo.currentTime = currentTime;
}
drawFrame();
}
requestAnimationFrame(renderLoop);
}
function initVideoScroll() {
if (!heroVideo.duration || !isFinite(heroVideo.duration)) return;
if (videoReady) return;
videoReady = true;
heroVideo.currentTime = 0;
currentTime = 0;
targetTime = 0;
/* GSAP sets TARGET time — render loop lerps toward it */
var proxy = { t: 0 };
gsap.to(proxy, {
t: 1,
ease: 'none',
scrollTrigger: {
trigger: heroSection,
start: 'top top',
end: 'bottom bottom',
scrub: 0
},
onUpdate: function () {
targetTime = proxy.t * heroVideo.duration;
}
});
requestAnimationFrame(renderLoop);
setTimeout(drawFrame, 200);
/* Headline parallax + fade */
if (heroHeadline) {
gsap.to(heroHeadline, {
y: 200, opacity: 0, ease: 'none',
scrollTrigger: {
trigger: heroSection,
start: 'top top',
end: '30% top',
scrub: 1.5
}
});
}
}
heroVideo.addEventListener('loadedmetadata', function () { heroVideo.currentTime = 0; });
heroVideo.addEventListener('canplaythrough', initVideoScroll);
if (heroVideo.readyState >= 4) initVideoScroll();
}
/* ── Scroll reveal ── */
gsap.utils.toArray('[data-reveal]').forEach(function (el) {
gsap.from(el, {
y: 60, opacity: 0, duration: 1, ease: 'power3.out',
scrollTrigger: { trigger: el, start: 'top 85%', toggleActions: 'play none none none' }
});
});
gsap.utils.toArray('[data-reveal-stagger]').forEach(function (container) {
gsap.from(container.children, {
y: 50, opacity: 0, duration: 0.8, stagger: 0.15, ease: 'power3.out',
scrollTrigger: { trigger: container, start: 'top 80%', toggleActions: 'play none none none' }
});
});
/* ── Navbar — transparent → solid on scroll ── */
var nav = document.querySelector('nav');
if (nav) {
var header = nav.closest('header');
ScrollTrigger.create({
start: 60,
onUpdate: function (self) {
if (self.direction === 1 && window.scrollY > 60) {
header.style.background = 'rgba(13,13,13,0.95)';
header.style.backdropFilter = 'blur(20px)';
} else if (window.scrollY <= 60) {
header.style.background = 'transparent';
header.style.backdropFilter = 'blur(0px)';
}
}
});
}
})();Key parameters to tune
| Parameter | Default | Effect |
h-[500vh] on section | 500vh | More height = slower video relative to scroll |
lerpFactor | 0.06 | Lower = smoother but more lag; higher = snappier |
scrub on GSAP | 0 | 0 = instant target update; higher = GSAP also smooths |
crf in ffmpeg | 22 | Lower = better quality, bigger file |
Lenis duration | 1.6 | Higher = more momentum in scroll |
Checklist before deploying
- Video re-encoded with
-g 1 -keyint_min 1(every frame is a keyframe) - Audio stripped with
-an +faststartflag set for web delivery- Canvas has
devicePixelRatioscaling for retina displays - Video has
preload="auto"andplaysinlineattributes - Gradient overlay ensures text readability over video
- Grain overlay for cinematic feel (optional — remove if not needed)
data-revealattributes on content sections below the video
Troubleshooting
Video jumps/stutters: Re-encode with -g 1. This is almost always the cause.
Video doesn't play at all: Check canplaythrough event fires. Ensure preload="auto".
Black canvas: Video may not have loaded yet. The setTimeout(drawFrame, 200) handles initial
paint, but on slow connections increase the delay or add a loading indicator.
Headline doesn't fade: Verify hero-headline ID exists and section height is tall enough.
Content below hero invisible: Check data-reveal scroll triggers aren't hiding elements that
should be visible. First section after hero should NOT have data-reveal if it starts in viewport.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

