4 Pure CSS3 with JSPublished 2026-10-05Free · no sign-up
Seven scroll effects, each running inside its own scrolling panel: a fade-up reveal with IntersectionObserver, the same reveal with the native view() timeline, sticky scrollytelling steps, text that slides sideways, images that grow as they enter, a staggered list and a header that shrinks.
Items fade and rise as they enter a scrolling panel, triggered by an IntersectionObserver that adds a class once and stops watching. Content is visible by default and only hidden when the script runs, so nothing is lost if JavaScript fails.
View code
<div class="sa1" tabindex="0" role="region" aria-label="Scroll this panel"><p class="hint">Scroll down ↓</p><div class="it">Plan your week</div><div class="it">Share with your team</div><div class="it">Track what shipped</div><div class="it">Celebrate</div></div>
var box = document.querySelector('.sa1'); box.classList.add('js');
var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { root: box, threshold: .3 });
box.querySelectorAll('.it').forEach(function (el) { io.observe(el); });
02. Native view() Timeline Reveal
Scrollanimation-timeline
The same reveal with no JavaScript: animation-timeline: view() ties each card's animation to its own position in the scroll container, and animation-range: entry limits it to the moment it scrolls in. Browsers without support skip the rule inside @supports and show the cards normally.
A sticky visual stays put while text steps scroll past, and the visual changes colour and label to match the step in view. An IntersectionObserver with a narrow rootMargin picks the active step, and each step's text is complete on its own.
View code
<div class="sa3" tabindex="0" role="region" aria-label="How it works, scroll"><div class="vis" aria-hidden="true">1</div><div class="steps"><p data-s="1" data-c="#2563eb">Upload your product photos.</p><p data-s="2" data-c="#7c3aed">We remove backgrounds automatically.</p><p data-s="3" data-c="#db2777">Download a ready-to-use catalogue.</p></div></div>
var box = document.querySelector('.sa3'), vis = box.querySelector('.vis');
var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (!e.isIntersecting) return; box.querySelectorAll('p').forEach(function (p) { p.classList.toggle('on', p === e.target); }); vis.textContent = e.target.dataset.s; vis.style.background = e.target.dataset.c; }); }, { root: box, rootMargin: '-55% 0px -40% 0px' });
box.querySelectorAll('p').forEach(function (p) { io.observe(p); });
04. Text That Slides Sideways on Scroll
Scrollanimation-timeline
A huge line of text slides horizontally as the panel scrolls vertically, a common effect on agency sites. animation-timeline: scroll() links the keyframes to the container's scroll position. Without support the text simply sits still.
Each image starts slightly small and rounded and grows to full size as it scrolls into view, then stays. view() with animation-range: entry 0% cover 40% makes the growth finish before the image reaches the middle of the panel.
When the list scrolls into view, its items slide in one by one. One IntersectionObserver watches the list, not each item, and adds a class; each item's delay comes from its --i custom property, so the stagger is pure CSS.
View code
<div class="sa6" tabindex="0" role="region" aria-label="Scroll this panel"><p>Scroll ↓</p><ul><li style="--i:0">Free shipping over $50</li><li style="--i:1">30-day returns</li><li style="--i:2">Two-year warranty</li><li style="--i:3">Carbon-neutral delivery</li></ul></div>
var box = document.querySelector('.sa6'), ul = box.querySelector('ul'); box.classList.add('js');
var io = new IntersectionObserver(function (es) { if (es[0].isIntersecting) { ul.classList.add('in'); io.disconnect(); } }, { root: box, threshold: .4 });
io.observe(ul);
07. Header That Shrinks on Scroll
ScrollHeader
A tall header with a big title shrinks to a compact bar once the content scrolls, using a scroll-driven animation on the panel. The header is position: sticky so it stays visible, and in older browsers it simply stays tall.
View code
<div class="sa7" tabindex="0" role="region" aria-label="Article, scroll"><header><b>Journal</b></header><article><p>Scroll the panel to see the header shrink.</p><p>Short paragraphs of text stand in for an article.</p><p>The header keeps its links reachable while it is small.</p><p>It returns to full size when you scroll back up.</p><p>End of the demo article.</p></article></div>
IntersectionObserver works everywhere and fires once when an element enters view, which suits reveals. CSS scroll-driven animations (animation-timeline: scroll() and view()) tie progress to the scroll position itself and need no script.
Never hide content for good
If a reveal starts from opacity: 0, add that starting state only when the script has run or inside @supports (animation-timeline: view()). Otherwise a failed script leaves an empty page.
Pick the range
With view timelines, animation-range: entry 0% entry 100% runs the animation only while the element is entering. Ranges such as cover 40% let it finish before the element reaches the middle.
Motion that follows scrolling
Effects tied to scroll can make some people feel unwell. Stop them under prefers-reduced-motion, and keep the movement small: a fade and 20 to 30 pixels of travel is plenty.
Frequently asked questions
Which browsers support animation-timeline?
Chromium-based browsers and Safari 26 do; Firefox is adding it. The demos fall back to static content where it is missing.
Why are the demos in scrolling panels?
So each effect can be tried inside the preview. On a real page, drop the panel and the timelines use the page scroll.
Do scroll animations affect SEO?
Not when the content is in the HTML and visible without script, as in every demo here.