
Pure CSS Parallax Hero with Scroll Timeline
Published
A CSS parallax effect is depth on scroll — multiple layers at different rates, or the whole viewport moving as if travelling through a scene. These 22 hand-coded patterns cover pure-CSS scroll-timeline hero, animation-timeline: view() per-element enter, SVG path draw, split-screen, backdrop-blur transitions, mouse-position cards, 3D perspective (Keith Clark), pinned-scene product showcase, chapter storytelling, aurora gradient, plus classic landscape, sticky sections, horizontal scroll, and zoom-in depth. Every demo is scoped under .plx-NN, uses transform + opacity only for 60fps, and guards motion behind prefers-reduced-motion.
Related11 CSS animation-timeline Demos20 CSS Scroll Animations31 CSS Hero Sections25 CSS Background Animations12 CSS Full Page Sections

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated
animation-timeline: scroll(root) and animation-timeline: view(), both Baseline widely-available since 2024 (Chromium 115+, Safari 18+, Firefox 130+). You define a normal @keyframes block interpolating transform: translateY() from a start position to an end position, then bind that animation to a scroll timeline instead of a wall-clock timeline. The browser advances the animation as the viewer scrolls the page — the layer moves at whatever rate the keyframes describe. No requestAnimationFrame, no scroll listener, no INP penalty. Demo 01 is the reference: 4 depth layers (sky / distant mountains / mid-hills / foreground trees) each get their own @keyframes binding a different translateY range to the same scroll timeline, wrap the whole rule set in @supports (animation-timeline: scroll()) with a static hero fallback declared first. For pinned parallax scenes where an element sits fixed while inner content animates, animation-timeline: view(block 20% 80%) binds the animation to the element's own viewport entry — Demo 02 shows the pattern with a card grid where each card animates independently based on its own scroll position, not the page's.background-attachment: fixed on a section background — has been broken on mobile since 2011. iOS Safari ignored it from iOS 5 onward because Apple decided the fixed-background scroll calculation hurt scroll performance enough to skip it entirely; modern Chrome on Android followed the same convention. The result: tutorials that teach this pattern produce sites that look correct on desktop but have static, unfixed backgrounds on phones — exactly where parallax is most visually impactful. Every demo in this collection avoids background-attachment: fixed and uses transform: translateY() (either via animation-timeline: scroll() for pure CSS or via a requestAnimationFrame-throttled scroll listener for mouse-tracking demos). Transform is a compositor-only property, runs at 60fps on the GPU thread, works identically on iOS and Android, and never triggers layout or paint.perspective: 1px on the scroll container, then apply transform: translateZ(-2px) scale(3) to background layers and transform: translateZ(0) to foreground layers. As the viewer scrolls, deeper layers move slower because they're farther from the camera in 3D space — the browser does the parallax math automatically as part of the 3D projection. Zero JavaScript. Two important limitations to know before shipping: first, the scroll container must own its own scroll (typically overflow-y: auto on the wrapper, not body) — the technique doesn't work with document-level scroll. Second, the perspective value creates a stacking context that interferes with position: fixed children — any fixed header, modal, or CTA inside the container will be positioned relative to the container, not the viewport. Demo 08 is the reference implementation with an inline annotation panel explaining what the perspective is doing at each layer. For 2026 production work, animation-timeline: scroll() (Demo 01) is usually the cleaner choice — no stacking context problems, no scroll-container requirement. Keith's technique remains useful for demos where you want depth to feel like physical 3D space rather than programmed motion.prefers-reduced-motion: reduce for any interaction-triggered motion; scroll is an interaction. Every demo in this collection includes a @media (prefers-reduced-motion: reduce) block that disables the parallax and shows the static end-state — all layers at their natural position, no translateY offset, no scroll-linked animation. The classic mistake: authoring a reduced-motion block that only removes the animation. If your layers start at translateY(-500px) and animation is what moves them to translateY(0), removing the animation leaves them stuck at -500px — invisible. Correct pattern: reset the transform inside the reduced-motion block AS WELL as removing the animation. @media (prefers-reduced-motion: reduce) { .layer { animation: none !important; transform: none !important; } }. Also budget for users who leave reduced-motion off but need cognitive relief — every scroll-driven demo in this collection uses subtle 20-40% parallax offsets rather than aggressive 100%+ offsets. Big parallax swings are the ones that trigger motion sickness in neurotypical users too.animation-timeline: scroll(root) binds the animation to the SCROLL POSITION of the nearest scrollable ancestor (usually the document root) — the animation progresses from 0% to 100% as the user scrolls from top of scroll range to bottom. Every scroll-linked element on the page runs off the same timeline. animation-timeline: view() binds the animation to the VIEWPORT ENTRY POSITION of the element itself — the animation progresses from 0% to 100% as the element enters and exits the viewport, independent of the document's total scroll. Use scroll() for global scroll-linked effects (page-level parallax hero, top progress bar, section-based aurora shift — Demos 01, 06, 11). Use view() for per-element effects (each card in a grid animating as it enters, each image gallery item revealing on its own scroll — Demos 02, 17). The view(block 20% 80%) parameter tunes when the animation starts and ends — 20% means the animation starts when the element is 20% into the viewport from the bottom, and ends at 80%. Demo 02 uses this to make cards fully settled by the time they're centered, not still animating.transform, opacity) for every parallax layer — never top, left, margin, width, or height. These properties promote the layer to a GPU compositor thread and avoid triggering layout or paint on scroll. Second, prefer animation-timeline: scroll() over window.addEventListener('scroll', ...) — the CSS mechanism runs entirely off the main thread and never contributes to INP (Interaction to Next Paint), whereas even a requestAnimationFrame-throttled scroll listener can push INP over 200ms on mid-tier Android when it does layout math. Third, add will-change: transform to active parallax layers so the browser proactively promotes them to compositor threads — but remove will-change when the animation completes (via animation-fill-mode or a JS timer removing the class) because a permanent will-change layer costs GPU memory. Fourth, budget for LCP: your hero image or first paint should NOT be blocked by parallax setup. Ship the hero visible in its final-first-frame state, then apply the parallax bindings after — a scroll-timeline animation starts at scroll position 0 by default, so the initial paint is the animation's 0% keyframe, which should match the static production hero. Verified: every scroll-timeline demo in this collection tests at LCP < 2.5s, INP < 200ms, CLS < 0.1 on Slow 4G with Lighthouse.animation-timeline: scroll() / view() PRIMITIVE as a reference — this collection USES those primitives but the subject is the parallax pattern, not the primitive itself. If you need a fade-in card, use css-scroll-animations; if you need a photo curtain reveal, use css-image-reveal-scroll; if you need multiple layers or a whole scene moving at different rates, this is the collection..plx-NN root that drops into any framework without leaking. Tailwind v3 users translate the CSS to @apply inside @layer components; Tailwind v4 uses CSS-first config so --font-display, --surface, --ink, --parallax-speed tokens can be declared in @theme. React and Next.js apps wrap the demo as a <ParallaxHero /> component; the scroll-timeline demos need no React state — the animation lives entirely in CSS. For the JS-using demos (mouse-tracking, chapter markers), the handler can be lifted into a useEffect cleanup pattern with useCallback for the scroll/mouse listener. Vue 3 wraps in a <template> block with the scroll-timeline CSS applied via a scoped <style> block; Composition API onMounted + onUnmounted for the JS handlers. Svelte works with <style> blocks and onMount/onDestroy lifecycle. Astro islands (client:visible for JS demos, static for pure-CSS) handle both cases with no framework glue. SolidJS, Qwik, and Angular signals all map to the same pattern. The animation-timeline demos are the most future-proof — zero framework code needed, the CSS runs identically across every stack, and the visual result never depends on hydration timing.20 hand-coded CSS animated buttons — neon glow, ripple, 3D press, liquid fill, jelly bounce, shine sweep, animated border, moving gradient CTA, text flip, submit success state, add-to-cart progress, download icon, hamburger-to-close, toggle switch, loading spinner inside button, next/prev arrow nav, and ghost button background reveal. Half pure CSS, half lightweight JS for production interactions.
11 pure CSS animation-timeline demos built on the W3C scroll() and view() spec — reading progress bar, view-timeline fade & reveal, shrinking sticky header, stacking cards, horizontal scroll section, parallax hero, container shadow indicators, reverse-scroll columns, text scrim reveal, cover card to sticky header, and image zoom / clip-path wipe. Zero JS, off the main thread, Core Web Vitals safe. Copy-paste ready.
25 hand-coded CSS background animations for SaaS hero sections, developer tool documentation, agency portfolios, gaming and Web3 landing pages, seasonal e-commerce campaigns, and dark-mode dashboards. Covers full-screen gradient shifts, animated mesh gradients, aurora borealis glow, cursor spotlight follow, interactive water ripples, dot grid and diagonal stripe patterns, SVG grain and noise texture overlays, topographic contour lines, layered SVG waves, plasma and lava lamp fluids, bokeh light blur, particle constellation networks, matrix digital rain, synthwave 3D perspective grids, starfield warp speed, CRT scanline overlays, fog and mist drift, morphing organic blobs, floating glassmorphism orbs, halftone dot patterns, starry night skies, floating geometric shapes, rising bubbles, and falling snow. 20 are pure CSS with zero JavaScript; the 5 that need it use vanilla canvas or pointer tracking with no dependencies. Every background animates compositor-only properties so it never blocks the main thread, is scoped under a .bga-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.