05 / 22
All 22 in 22 CSS Parallax Effects
- 01Pure CSS Parallax Hero with Scroll Timeline
- 02Animation Timeline View Element Enter Parallax
- 03Scroll Driven SVG Path Draw Parallax
- 04Split Screen Dual Axis Parallax Scroll
- 05Backdrop Filter Blur Cross Fade Parallax
- 06Scroll Linked Typography Scale Parallax
- 07Mouse Position Parallax Card Grid
- 083D Transform Perspective Layer Parallax
- 09Parallax Product Showcase with Pinned Scene
- 10Parallax Storytelling with Chapter Markers
- 11Scroll Driven Aurora Gradient Parallax
- 12Parallax Landing Page Hero with Depth Layers
- 13CSS Parallax Hero Section with Layered Scroll
- 14Multi Layer Parallax Landscape Illustration
- 15Sticky Parallax Sections Stripe Linear Style
- 16Multi Scene Parallax Scrolling Journey
- 17Parallax Image Grid Asymmetric Gallery
- 18Horizontal Parallax Scroll Panels
- 19Parallax Text Overlay Texture Effect
- 20Parallax Card Hover with Mouse Tracking
- 21Zoom In Depth Parallax on Scroll
- 22Parallax Background Blur Transition
Backdrop Filter Blur Cross Fade Parallax
A focus-pull between two marketing sections: as the incoming panel rises, a glass veil over the outgoing scene ramps its backdrop-filter blur from zero to full while the scene behind it drifts back. The outgoing section stays pinned and legible until the moment it defocuses, which is what makes the handoff feel filmic rather than abrupt.
Build UI like this from scratch: Flexbox, Grid, animations & responsive CSS
ZTM · The CSS Bootcamp · 30‑day money-back guarantee · Affiliate link
The code
<div class="plx-05">
<section class="plx-05__pin" aria-labelledby="plx-05-a">
<div class="plx-05__scene">
<div class="plx-05__plane" aria-hidden="true">
<span class="plx-05__orb plx-05__orb--1"></span>
<span class="plx-05__orb plx-05__orb--2"></span>
<span class="plx-05__grid"></span>
</div>
<div class="plx-05__sceneCopy">
<p class="plx-05__eyebrow">Northwind Platform</p>
<h2 class="plx-05__h1" id="plx-05-a">Every event, in order, forever</h2>
<p class="plx-05__lede">An append-only log your services can replay from any point in the last ninety days.</p>
</div>
<div class="plx-05__veil" aria-hidden="true"></div>
</div>
</section>
<section class="plx-05__incoming" aria-labelledby="plx-05-b">
<div class="plx-05__card">
<p class="plx-05__eyebrow plx-05__eyebrow--alt">Now in the same pipeline</p>
<h2 class="plx-05__h2" id="plx-05-b">Replay, not reprocessing</h2>
<p class="plx-05__p">Point a consumer at any offset and it rebuilds its own state. No backfill scripts, no separate batch path, no second copy of the data to keep honest.</p>
<ul class="plx-05__list">
<li>Ordered delivery per partition key</li>
<li>Ninety days of retention, configurable per topic</li>
<li>Exactly-once sinks for Postgres and ClickHouse</li>
</ul>
<a class="plx-05__cta" href="#plx-05-b">Read the architecture note</a>
</div>
</section>
<section class="plx-05__after">
<p class="plx-05__p">Scroll back up and the focus pull runs in reverse — the veil's blur is bound to scroll position, not to a one-shot trigger.</p>
</section>
</div><div class="plx-05">
<section class="plx-05__pin" aria-labelledby="plx-05-a">
<div class="plx-05__scene">
<div class="plx-05__plane" aria-hidden="true">
<span class="plx-05__orb plx-05__orb--1"></span>
<span class="plx-05__orb plx-05__orb--2"></span>
<span class="plx-05__grid"></span>
</div>
<div class="plx-05__sceneCopy">
<p class="plx-05__eyebrow">Northwind Platform</p>
<h2 class="plx-05__h1" id="plx-05-a">Every event, in order, forever</h2>
<p class="plx-05__lede">An append-only log your services can replay from any point in the last ninety days.</p>
</div>
<div class="plx-05__veil" aria-hidden="true"></div>
</div>
</section>
<section class="plx-05__incoming" aria-labelledby="plx-05-b">
<div class="plx-05__card">
<p class="plx-05__eyebrow plx-05__eyebrow--alt">Now in the same pipeline</p>
<h2 class="plx-05__h2" id="plx-05-b">Replay, not reprocessing</h2>
<p class="plx-05__p">Point a consumer at any offset and it rebuilds its own state. No backfill scripts, no separate batch path, no second copy of the data to keep honest.</p>
<ul class="plx-05__list">
<li>Ordered delivery per partition key</li>
<li>Ninety days of retention, configurable per topic</li>
<li>Exactly-once sinks for Postgres and ClickHouse</li>
</ul>
<a class="plx-05__cta" href="#plx-05-b">Read the architecture note</a>
</div>
</section>
<section class="plx-05__after">
<p class="plx-05__p">Scroll back up and the focus pull runs in reverse — the veil's blur is bound to scroll position, not to a one-shot trigger.</p>
</section>
</div>.plx-05 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--font-display: "Geist", "Inter", system-ui, -apple-system, sans-serif;
--font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
--bg: #070d1c;
--surface: #0d1730;
--ink: #e9eefb;
--muted: #8fa1c7;
--accent: #4cc4ff;
background: var(--bg);
color: var(--ink);
font-family: var(--font-body);
overflow-x: clip;
}
.plx-05 * {
box-sizing: border-box;
}
.plx-05__pin {
position: sticky;
top: 0;
block-size: 100svh;
overflow: clip;
z-index: 0;
}
.plx-05__scene {
position: relative;
block-size: 100%;
display: flex;
align-items: center;
}
.plx-05__plane {
position: absolute;
inset: -10% -5%;
will-change: transform;
}
.plx-05__orb {
position: absolute;
border-radius: 50%;
filter: blur(30px);
}
.plx-05__orb--1 {
inline-size: 44vmax;
aspect-ratio: 1;
top: -12%;
right: -8%;
background: radial-gradient(circle, rgba(76,196,255,0.45), rgba(76,196,255,0) 68%);
}
.plx-05__orb--2 {
inline-size: 34vmax;
aspect-ratio: 1;
bottom: -14%;
left: -6%;
background: radial-gradient(circle, rgba(120,86,255,0.4), rgba(120,86,255,0) 70%);
}
.plx-05__grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(160,190,255,0.09) 1px, transparent 1px), linear-gradient(90deg, rgba(160,190,255,0.09) 1px, transparent 1px);
background-size: 64px 64px;
mask-image: radial-gradient(70% 60% at 50% 45%, #000 30%, transparent 100%);
}
.plx-05__sceneCopy {
position: relative;
z-index: 2;
padding: clamp(24px, 6vw, 88px);
max-inline-size: 44rem;
min-inline-size: 0;
}
.plx-05__eyebrow {
margin: 0 0 16px;
font: 600 10px/1 var(--font-body);
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--accent);
}
.plx-05__eyebrow--alt {
color: #6f86b6;
}
.plx-05__h1 {
margin: 0 0 18px;
font-family: var(--font-display);
font-size: clamp(32px, 7vw, 72px);
font-weight: 620;
line-height: 1.02;
letter-spacing: -0.03em;
}
.plx-05__lede {
margin: 0;
max-inline-size: 30rem;
font-size: clamp(15px, 3.4vw, 19px);
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__veil {
position: absolute;
inset: 0;
z-index: 3;
background: rgba(7,13,28,0.34);
pointer-events: none;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
background: rgba(7,13,28,0.12);
backdrop-filter: blur(0px);
}
}
.plx-05__incoming {
position: relative;
z-index: 4;
min-block-size: 100svh;
display: flex;
align-items: center;
padding: clamp(28px, 6vw, 88px);
background: linear-gradient(180deg, rgba(9,16,34,0.2) 0%, #091022 26%, #091022 100%);
will-change: transform;
}
.plx-05__card {
inline-size: 100%;
max-inline-size: 40rem;
min-inline-size: 0;
padding: clamp(22px, 4vw, 40px);
border-radius: 22px;
border: 1px solid rgba(158,196,255,0.18);
background: linear-gradient(180deg, rgba(30,48,90,0.62), rgba(13,23,48,0.72));
box-shadow: inset 0 1px 0 rgba(226,240,255,0.22), 0 30px 70px -40px rgba(0,0,0,0.9);
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__card {
backdrop-filter: blur(14px);
}
}
.plx-05__h2 {
margin: 0 0 14px;
font-family: var(--font-display);
font-size: clamp(24px, 5vw, 40px);
font-weight: 620;
line-height: 1.08;
letter-spacing: -0.02em;
}
.plx-05__p {
margin: 0 0 20px;
font-size: clamp(15px, 3.2vw, 17px);
line-height: 1.7;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__list {
margin: 0 0 26px;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
font-size: 15px;
color: #c6d5f2;
}
.plx-05__list li {
padding-inline-start: 18px;
position: relative;
min-inline-size: 0;
}
.plx-05__list li::before {
content: "";
position: absolute;
inset-inline-start: 0;
top: 0.55em;
inline-size: 7px;
block-size: 7px;
border-radius: 50%;
background: var(--accent);
}
.plx-05__cta {
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 22px;
border-radius: 12px;
background: var(--accent);
color: #04121f;
font: 600 14px/1 var(--font-body);
text-decoration: none;
}
.plx-05__cta:hover {
background: #86d8ff;
}
.plx-05__cta:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 3px;
}
.plx-05__after {
position: relative;
z-index: 4;
padding: clamp(40px, 8vw, 96px) clamp(28px, 6vw, 88px) 130px;
background: #091022;
}
.plx-05__after .plx-05__p {
max-inline-size: 32rem;
margin: 0;
}
@supports (animation-timeline: scroll()) {
.plx-05__plane {
animation: plx-05-plane linear both;
animation-timeline: scroll(root);
}
.plx-05__incoming {
animation: plx-05-rise linear both;
animation-timeline: scroll(root);
animation-range: 18% 60%;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
animation: plx-05-pull linear both;
animation-timeline: scroll(root);
animation-range: 22% 62%;
}
}
}
@keyframes plx-05-plane {
to {
transform: translateY(-90px) scale(1.06);
}
}
@keyframes plx-05-rise {
from {
transform: translateY(12vh);
}
to {
transform: translateY(0);
}
}
@keyframes plx-05-pull {
from {
backdrop-filter: blur(0px);
background: rgba(7,13,28,0.05);
}
to {
backdrop-filter: blur(18px);
background: rgba(7,13,28,0.55);
}
}
@media (prefers-reduced-motion: reduce) {
.plx-05__plane,
.plx-05__incoming,
.plx-05__veil {
animation: none !important;
transform: none !important;
will-change: auto;
}
.plx-05__veil {
backdrop-filter: none !important;
background: rgba(7,13,28,0.1);
}
}.plx-05 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--font-display: "Geist", "Inter", system-ui, -apple-system, sans-serif;
--font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
--bg: #070d1c;
--surface: #0d1730;
--ink: #e9eefb;
--muted: #8fa1c7;
--accent: #4cc4ff;
background: var(--bg);
color: var(--ink);
font-family: var(--font-body);
overflow-x: clip;
}
.plx-05 * {
box-sizing: border-box;
}
.plx-05__pin {
position: sticky;
top: 0;
block-size: 100svh;
overflow: clip;
z-index: 0;
}
.plx-05__scene {
position: relative;
block-size: 100%;
display: flex;
align-items: center;
}
.plx-05__plane {
position: absolute;
inset: -10% -5%;
will-change: transform;
}
.plx-05__orb {
position: absolute;
border-radius: 50%;
filter: blur(30px);
}
.plx-05__orb--1 {
inline-size: 44vmax;
aspect-ratio: 1;
top: -12%;
right: -8%;
background: radial-gradient(circle, rgba(76,196,255,0.45), rgba(76,196,255,0) 68%);
}
.plx-05__orb--2 {
inline-size: 34vmax;
aspect-ratio: 1;
bottom: -14%;
left: -6%;
background: radial-gradient(circle, rgba(120,86,255,0.4), rgba(120,86,255,0) 70%);
}
.plx-05__grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(160,190,255,0.09) 1px, transparent 1px), linear-gradient(90deg, rgba(160,190,255,0.09) 1px, transparent 1px);
background-size: 64px 64px;
mask-image: radial-gradient(70% 60% at 50% 45%, #000 30%, transparent 100%);
}
.plx-05__sceneCopy {
position: relative;
z-index: 2;
padding: clamp(24px, 6vw, 88px);
max-inline-size: 44rem;
min-inline-size: 0;
}
.plx-05__eyebrow {
margin: 0 0 16px;
font: 600 10px/1 var(--font-body);
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--accent);
}
.plx-05__eyebrow--alt {
color: #6f86b6;
}
.plx-05__h1 {
margin: 0 0 18px;
font-family: var(--font-display);
font-size: clamp(32px, 7vw, 72px);
font-weight: 620;
line-height: 1.02;
letter-spacing: -0.03em;
}
.plx-05__lede {
margin: 0;
max-inline-size: 30rem;
font-size: clamp(15px, 3.4vw, 19px);
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__veil {
position: absolute;
inset: 0;
z-index: 3;
background: rgba(7,13,28,0.34);
pointer-events: none;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
background: rgba(7,13,28,0.12);
backdrop-filter: blur(0px);
}
}
.plx-05__incoming {
position: relative;
z-index: 4;
min-block-size: 100svh;
display: flex;
align-items: center;
padding: clamp(28px, 6vw, 88px);
background: linear-gradient(180deg, rgba(9,16,34,0.2) 0%, #091022 26%, #091022 100%);
will-change: transform;
}
.plx-05__card {
inline-size: 100%;
max-inline-size: 40rem;
min-inline-size: 0;
padding: clamp(22px, 4vw, 40px);
border-radius: 22px;
border: 1px solid rgba(158,196,255,0.18);
background: linear-gradient(180deg, rgba(30,48,90,0.62), rgba(13,23,48,0.72));
box-shadow: inset 0 1px 0 rgba(226,240,255,0.22), 0 30px 70px -40px rgba(0,0,0,0.9);
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__card {
backdrop-filter: blur(14px);
}
}
.plx-05__h2 {
margin: 0 0 14px;
font-family: var(--font-display);
font-size: clamp(24px, 5vw, 40px);
font-weight: 620;
line-height: 1.08;
letter-spacing: -0.02em;
}
.plx-05__p {
margin: 0 0 20px;
font-size: clamp(15px, 3.2vw, 17px);
line-height: 1.7;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__list {
margin: 0 0 26px;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
font-size: 15px;
color: #c6d5f2;
}
.plx-05__list li {
padding-inline-start: 18px;
position: relative;
min-inline-size: 0;
}
.plx-05__list li::before {
content: "";
position: absolute;
inset-inline-start: 0;
top: 0.55em;
inline-size: 7px;
block-size: 7px;
border-radius: 50%;
background: var(--accent);
}
.plx-05__cta {
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 22px;
border-radius: 12px;
background: var(--accent);
color: #04121f;
font: 600 14px/1 var(--font-body);
text-decoration: none;
}
.plx-05__cta:hover {
background: #86d8ff;
}
.plx-05__cta:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 3px;
}
.plx-05__after {
position: relative;
z-index: 4;
padding: clamp(40px, 8vw, 96px) clamp(28px, 6vw, 88px) 130px;
background: #091022;
}
.plx-05__after .plx-05__p {
max-inline-size: 32rem;
margin: 0;
}
@supports (animation-timeline: scroll()) {
.plx-05__plane {
animation: plx-05-plane linear both;
animation-timeline: scroll(root);
}
.plx-05__incoming {
animation: plx-05-rise linear both;
animation-timeline: scroll(root);
animation-range: 18% 60%;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
animation: plx-05-pull linear both;
animation-timeline: scroll(root);
animation-range: 22% 62%;
}
}
}
@keyframes plx-05-plane {
to {
transform: translateY(-90px) scale(1.06);
}
}
@keyframes plx-05-rise {
from {
transform: translateY(12vh);
}
to {
transform: translateY(0);
}
}
@keyframes plx-05-pull {
from {
backdrop-filter: blur(0px);
background: rgba(7,13,28,0.05);
}
to {
backdrop-filter: blur(18px);
background: rgba(7,13,28,0.55);
}
}
@media (prefers-reduced-motion: reduce) {
.plx-05__plane,
.plx-05__incoming,
.plx-05__veil {
animation: none !important;
transform: none !important;
will-change: auto;
}
.plx-05__veil {
backdrop-filter: none !important;
background: rgba(7,13,28,0.1);
}
}Here's a working CSS Parallax Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Backdrop Filter Blur Cross Fade Parallax
Source: https://codefronts.com/motion/css-parallax-designs/backdrop-filter-blur-cross-fade-parallax/
A focus-pull between two marketing sections: as the incoming panel rises, a glass veil over the outgoing scene ramps its backdrop-filter blur from zero to full while the scene behind it drifts back. The outgoing section stays pinned and legible until the moment it defocuses, which is what makes the handoff feel filmic rather than abrupt.
## HTML
```html
<div class="plx-05">
<section class="plx-05__pin" aria-labelledby="plx-05-a">
<div class="plx-05__scene">
<div class="plx-05__plane" aria-hidden="true">
<span class="plx-05__orb plx-05__orb--1"></span>
<span class="plx-05__orb plx-05__orb--2"></span>
<span class="plx-05__grid"></span>
</div>
<div class="plx-05__sceneCopy">
<p class="plx-05__eyebrow">Northwind Platform</p>
<h2 class="plx-05__h1" id="plx-05-a">Every event, in order, forever</h2>
<p class="plx-05__lede">An append-only log your services can replay from any point in the last ninety days.</p>
</div>
<div class="plx-05__veil" aria-hidden="true"></div>
</div>
</section>
<section class="plx-05__incoming" aria-labelledby="plx-05-b">
<div class="plx-05__card">
<p class="plx-05__eyebrow plx-05__eyebrow--alt">Now in the same pipeline</p>
<h2 class="plx-05__h2" id="plx-05-b">Replay, not reprocessing</h2>
<p class="plx-05__p">Point a consumer at any offset and it rebuilds its own state. No backfill scripts, no separate batch path, no second copy of the data to keep honest.</p>
<ul class="plx-05__list">
<li>Ordered delivery per partition key</li>
<li>Ninety days of retention, configurable per topic</li>
<li>Exactly-once sinks for Postgres and ClickHouse</li>
</ul>
<a class="plx-05__cta" href="#plx-05-b">Read the architecture note</a>
</div>
</section>
<section class="plx-05__after">
<p class="plx-05__p">Scroll back up and the focus pull runs in reverse — the veil's blur is bound to scroll position, not to a one-shot trigger.</p>
</section>
</div>
```
## CSS
```css
.plx-05 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--font-display: "Geist", "Inter", system-ui, -apple-system, sans-serif;
--font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
--bg: #070d1c;
--surface: #0d1730;
--ink: #e9eefb;
--muted: #8fa1c7;
--accent: #4cc4ff;
background: var(--bg);
color: var(--ink);
font-family: var(--font-body);
overflow-x: clip;
}
.plx-05 * {
box-sizing: border-box;
}
.plx-05__pin {
position: sticky;
top: 0;
block-size: 100svh;
overflow: clip;
z-index: 0;
}
.plx-05__scene {
position: relative;
block-size: 100%;
display: flex;
align-items: center;
}
.plx-05__plane {
position: absolute;
inset: -10% -5%;
will-change: transform;
}
.plx-05__orb {
position: absolute;
border-radius: 50%;
filter: blur(30px);
}
.plx-05__orb--1 {
inline-size: 44vmax;
aspect-ratio: 1;
top: -12%;
right: -8%;
background: radial-gradient(circle, rgba(76,196,255,0.45), rgba(76,196,255,0) 68%);
}
.plx-05__orb--2 {
inline-size: 34vmax;
aspect-ratio: 1;
bottom: -14%;
left: -6%;
background: radial-gradient(circle, rgba(120,86,255,0.4), rgba(120,86,255,0) 70%);
}
.plx-05__grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(160,190,255,0.09) 1px, transparent 1px), linear-gradient(90deg, rgba(160,190,255,0.09) 1px, transparent 1px);
background-size: 64px 64px;
mask-image: radial-gradient(70% 60% at 50% 45%, #000 30%, transparent 100%);
}
.plx-05__sceneCopy {
position: relative;
z-index: 2;
padding: clamp(24px, 6vw, 88px);
max-inline-size: 44rem;
min-inline-size: 0;
}
.plx-05__eyebrow {
margin: 0 0 16px;
font: 600 10px/1 var(--font-body);
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--accent);
}
.plx-05__eyebrow--alt {
color: #6f86b6;
}
.plx-05__h1 {
margin: 0 0 18px;
font-family: var(--font-display);
font-size: clamp(32px, 7vw, 72px);
font-weight: 620;
line-height: 1.02;
letter-spacing: -0.03em;
}
.plx-05__lede {
margin: 0;
max-inline-size: 30rem;
font-size: clamp(15px, 3.4vw, 19px);
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__veil {
position: absolute;
inset: 0;
z-index: 3;
background: rgba(7,13,28,0.34);
pointer-events: none;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
background: rgba(7,13,28,0.12);
backdrop-filter: blur(0px);
}
}
.plx-05__incoming {
position: relative;
z-index: 4;
min-block-size: 100svh;
display: flex;
align-items: center;
padding: clamp(28px, 6vw, 88px);
background: linear-gradient(180deg, rgba(9,16,34,0.2) 0%, #091022 26%, #091022 100%);
will-change: transform;
}
.plx-05__card {
inline-size: 100%;
max-inline-size: 40rem;
min-inline-size: 0;
padding: clamp(22px, 4vw, 40px);
border-radius: 22px;
border: 1px solid rgba(158,196,255,0.18);
background: linear-gradient(180deg, rgba(30,48,90,0.62), rgba(13,23,48,0.72));
box-shadow: inset 0 1px 0 rgba(226,240,255,0.22), 0 30px 70px -40px rgba(0,0,0,0.9);
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__card {
backdrop-filter: blur(14px);
}
}
.plx-05__h2 {
margin: 0 0 14px;
font-family: var(--font-display);
font-size: clamp(24px, 5vw, 40px);
font-weight: 620;
line-height: 1.08;
letter-spacing: -0.02em;
}
.plx-05__p {
margin: 0 0 20px;
font-size: clamp(15px, 3.2vw, 17px);
line-height: 1.7;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__list {
margin: 0 0 26px;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
font-size: 15px;
color: #c6d5f2;
}
.plx-05__list li {
padding-inline-start: 18px;
position: relative;
min-inline-size: 0;
}
.plx-05__list li::before {
content: "";
position: absolute;
inset-inline-start: 0;
top: 0.55em;
inline-size: 7px;
block-size: 7px;
border-radius: 50%;
background: var(--accent);
}
.plx-05__cta {
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 22px;
border-radius: 12px;
background: var(--accent);
color: #04121f;
font: 600 14px/1 var(--font-body);
text-decoration: none;
}
.plx-05__cta:hover {
background: #86d8ff;
}
.plx-05__cta:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 3px;
}
.plx-05__after {
position: relative;
z-index: 4;
padding: clamp(40px, 8vw, 96px) clamp(28px, 6vw, 88px) 130px;
background: #091022;
}
.plx-05__after .plx-05__p {
max-inline-size: 32rem;
margin: 0;
}
@supports (animation-timeline: scroll()) {
.plx-05__plane {
animation: plx-05-plane linear both;
animation-timeline: scroll(root);
}
.plx-05__incoming {
animation: plx-05-rise linear both;
animation-timeline: scroll(root);
animation-range: 18% 60%;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
animation: plx-05-pull linear both;
animation-timeline: scroll(root);
animation-range: 22% 62%;
}
}
}
@keyframes plx-05-plane {
to {
transform: translateY(-90px) scale(1.06);
}
}
@keyframes plx-05-rise {
from {
transform: translateY(12vh);
}
to {
transform: translateY(0);
}
}
@keyframes plx-05-pull {
from {
backdrop-filter: blur(0px);
background: rgba(7,13,28,0.05);
}
to {
backdrop-filter: blur(18px);
background: rgba(7,13,28,0.55);
}
}
@media (prefers-reduced-motion: reduce) {
.plx-05__plane,
.plx-05__incoming,
.plx-05__veil {
animation: none !important;
transform: none !important;
will-change: auto;
}
.plx-05__veil {
backdrop-filter: none !important;
background: rgba(7,13,28,0.1);
}
}
```Here's a working CSS Parallax Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Backdrop Filter Blur Cross Fade Parallax
Source: https://codefronts.com/motion/css-parallax-designs/backdrop-filter-blur-cross-fade-parallax/
A focus-pull between two marketing sections: as the incoming panel rises, a glass veil over the outgoing scene ramps its backdrop-filter blur from zero to full while the scene behind it drifts back. The outgoing section stays pinned and legible until the moment it defocuses, which is what makes the handoff feel filmic rather than abrupt.
## HTML
```html
<div class="plx-05">
<section class="plx-05__pin" aria-labelledby="plx-05-a">
<div class="plx-05__scene">
<div class="plx-05__plane" aria-hidden="true">
<span class="plx-05__orb plx-05__orb--1"></span>
<span class="plx-05__orb plx-05__orb--2"></span>
<span class="plx-05__grid"></span>
</div>
<div class="plx-05__sceneCopy">
<p class="plx-05__eyebrow">Northwind Platform</p>
<h2 class="plx-05__h1" id="plx-05-a">Every event, in order, forever</h2>
<p class="plx-05__lede">An append-only log your services can replay from any point in the last ninety days.</p>
</div>
<div class="plx-05__veil" aria-hidden="true"></div>
</div>
</section>
<section class="plx-05__incoming" aria-labelledby="plx-05-b">
<div class="plx-05__card">
<p class="plx-05__eyebrow plx-05__eyebrow--alt">Now in the same pipeline</p>
<h2 class="plx-05__h2" id="plx-05-b">Replay, not reprocessing</h2>
<p class="plx-05__p">Point a consumer at any offset and it rebuilds its own state. No backfill scripts, no separate batch path, no second copy of the data to keep honest.</p>
<ul class="plx-05__list">
<li>Ordered delivery per partition key</li>
<li>Ninety days of retention, configurable per topic</li>
<li>Exactly-once sinks for Postgres and ClickHouse</li>
</ul>
<a class="plx-05__cta" href="#plx-05-b">Read the architecture note</a>
</div>
</section>
<section class="plx-05__after">
<p class="plx-05__p">Scroll back up and the focus pull runs in reverse — the veil's blur is bound to scroll position, not to a one-shot trigger.</p>
</section>
</div>
```
## CSS
```css
.plx-05 {
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
--font-display: "Geist", "Inter", system-ui, -apple-system, sans-serif;
--font-body: system-ui, -apple-system, "Segoe UI", sans-serif;
--bg: #070d1c;
--surface: #0d1730;
--ink: #e9eefb;
--muted: #8fa1c7;
--accent: #4cc4ff;
background: var(--bg);
color: var(--ink);
font-family: var(--font-body);
overflow-x: clip;
}
.plx-05 * {
box-sizing: border-box;
}
.plx-05__pin {
position: sticky;
top: 0;
block-size: 100svh;
overflow: clip;
z-index: 0;
}
.plx-05__scene {
position: relative;
block-size: 100%;
display: flex;
align-items: center;
}
.plx-05__plane {
position: absolute;
inset: -10% -5%;
will-change: transform;
}
.plx-05__orb {
position: absolute;
border-radius: 50%;
filter: blur(30px);
}
.plx-05__orb--1 {
inline-size: 44vmax;
aspect-ratio: 1;
top: -12%;
right: -8%;
background: radial-gradient(circle, rgba(76,196,255,0.45), rgba(76,196,255,0) 68%);
}
.plx-05__orb--2 {
inline-size: 34vmax;
aspect-ratio: 1;
bottom: -14%;
left: -6%;
background: radial-gradient(circle, rgba(120,86,255,0.4), rgba(120,86,255,0) 70%);
}
.plx-05__grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(160,190,255,0.09) 1px, transparent 1px), linear-gradient(90deg, rgba(160,190,255,0.09) 1px, transparent 1px);
background-size: 64px 64px;
mask-image: radial-gradient(70% 60% at 50% 45%, #000 30%, transparent 100%);
}
.plx-05__sceneCopy {
position: relative;
z-index: 2;
padding: clamp(24px, 6vw, 88px);
max-inline-size: 44rem;
min-inline-size: 0;
}
.plx-05__eyebrow {
margin: 0 0 16px;
font: 600 10px/1 var(--font-body);
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--accent);
}
.plx-05__eyebrow--alt {
color: #6f86b6;
}
.plx-05__h1 {
margin: 0 0 18px;
font-family: var(--font-display);
font-size: clamp(32px, 7vw, 72px);
font-weight: 620;
line-height: 1.02;
letter-spacing: -0.03em;
}
.plx-05__lede {
margin: 0;
max-inline-size: 30rem;
font-size: clamp(15px, 3.4vw, 19px);
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__veil {
position: absolute;
inset: 0;
z-index: 3;
background: rgba(7,13,28,0.34);
pointer-events: none;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
background: rgba(7,13,28,0.12);
backdrop-filter: blur(0px);
}
}
.plx-05__incoming {
position: relative;
z-index: 4;
min-block-size: 100svh;
display: flex;
align-items: center;
padding: clamp(28px, 6vw, 88px);
background: linear-gradient(180deg, rgba(9,16,34,0.2) 0%, #091022 26%, #091022 100%);
will-change: transform;
}
.plx-05__card {
inline-size: 100%;
max-inline-size: 40rem;
min-inline-size: 0;
padding: clamp(22px, 4vw, 40px);
border-radius: 22px;
border: 1px solid rgba(158,196,255,0.18);
background: linear-gradient(180deg, rgba(30,48,90,0.62), rgba(13,23,48,0.72));
box-shadow: inset 0 1px 0 rgba(226,240,255,0.22), 0 30px 70px -40px rgba(0,0,0,0.9);
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__card {
backdrop-filter: blur(14px);
}
}
.plx-05__h2 {
margin: 0 0 14px;
font-family: var(--font-display);
font-size: clamp(24px, 5vw, 40px);
font-weight: 620;
line-height: 1.08;
letter-spacing: -0.02em;
}
.plx-05__p {
margin: 0 0 20px;
font-size: clamp(15px, 3.2vw, 17px);
line-height: 1.7;
color: var(--muted);
text-wrap: pretty;
}
.plx-05__list {
margin: 0 0 26px;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
font-size: 15px;
color: #c6d5f2;
}
.plx-05__list li {
padding-inline-start: 18px;
position: relative;
min-inline-size: 0;
}
.plx-05__list li::before {
content: "";
position: absolute;
inset-inline-start: 0;
top: 0.55em;
inline-size: 7px;
block-size: 7px;
border-radius: 50%;
background: var(--accent);
}
.plx-05__cta {
display: inline-flex;
align-items: center;
min-height: 46px;
padding: 0 22px;
border-radius: 12px;
background: var(--accent);
color: #04121f;
font: 600 14px/1 var(--font-body);
text-decoration: none;
}
.plx-05__cta:hover {
background: #86d8ff;
}
.plx-05__cta:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 3px;
}
.plx-05__after {
position: relative;
z-index: 4;
padding: clamp(40px, 8vw, 96px) clamp(28px, 6vw, 88px) 130px;
background: #091022;
}
.plx-05__after .plx-05__p {
max-inline-size: 32rem;
margin: 0;
}
@supports (animation-timeline: scroll()) {
.plx-05__plane {
animation: plx-05-plane linear both;
animation-timeline: scroll(root);
}
.plx-05__incoming {
animation: plx-05-rise linear both;
animation-timeline: scroll(root);
animation-range: 18% 60%;
}
@supports (backdrop-filter: blur(2px)) {
.plx-05__veil {
animation: plx-05-pull linear both;
animation-timeline: scroll(root);
animation-range: 22% 62%;
}
}
}
@keyframes plx-05-plane {
to {
transform: translateY(-90px) scale(1.06);
}
}
@keyframes plx-05-rise {
from {
transform: translateY(12vh);
}
to {
transform: translateY(0);
}
}
@keyframes plx-05-pull {
from {
backdrop-filter: blur(0px);
background: rgba(7,13,28,0.05);
}
to {
backdrop-filter: blur(18px);
background: rgba(7,13,28,0.55);
}
}
@media (prefers-reduced-motion: reduce) {
.plx-05__plane,
.plx-05__incoming,
.plx-05__veil {
animation: none !important;
transform: none !important;
will-change: auto;
}
.plx-05__veil {
backdrop-filter: none !important;
background: rgba(7,13,28,0.1);
}
}
```How this works
The blur belongs to a veil, not to the content. Blurring the outgoing section directly would blur its text at every scroll position and force a full-element re-raster. Instead an absolutely positioned veil sits over the scene with backdrop-filter: blur(0px), and only that filter value is animated. The section underneath stays a normal, crisp, selectable layer.
animation-range is what keeps it a transition. Bound to scroll(root) with no range, the blur would creep in from the first pixel of scroll. animation-range: 22% 62% confines the entire focus pull to the middle of the page, so the first screen is sharp, the handoff happens where the panel actually arrives, and the last screen is clean.
Two elements, one timeline, opposite jobs. The pinned scene's inner plane drifts up 90px while the incoming panel rises 12vh, both on the same scroll timeline. The differential is the parallax; the blur is only the atmosphere on top of it. Remove either and the transition flattens into a slide.
Filters are expensive and they create a containing block. A large blurred area on a low-end GPU is the one part of this pattern that can cost frames — keep the veil to the pinned viewport rather than the whole document. Also note that backdrop-filter establishes a containing block for fixed descendants, so any position: fixed UI inside the veil will anchor to the veil instead of the viewport.
Make it yours
- Change the end keyframe blur from
18pxto tune how far the focus pulls. - Move
animation-rangeto shift where in the page the handoff happens. - Add a
saturate(1.4)to the filter list for a warmer defocus. - Swap
--accentto retint the rim highlight and the CTA together. - Raise the incoming panel's
translateYdistance for a longer rise. - Give the veil a slight
brightness(0.85)for a darker fade-out.
Gotchas — read before shipping
backdrop-filteronly blurs what is painted behind the element, so a veil that is a sibling before the scene in paint order blurs nothing at all.- The property creates a containing block — a
position: fixednav inside the veil starts scrolling with it. - A full-page veil instead of a viewport-sized one blurs a very large area every frame and is the fastest way to lose 60fps on integrated graphics.
- Without
animation-rangethe blur ramps across the whole document and the first screen ships already soft.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 115+ | 18+ | 130+ | 115+ |
backdrop-filter is Baseline widely available; the scroll binding needs the scroll-driven animations module (2024). Where either is missing the veil falls back to a static translucent scrim and the sections simply follow one another.