10 CSS Parallax Effects 04 / 10
Multi-Scene Parallax Scrolling
Five full-bleed sections (Cosmos, Desert, Ocean, Forest, Finale) each with a slower-moving SVG background driving the parallax via a requestAnimationFrame-throttled scroll listener.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="plx-04" id="plx04">
<nav class="plx-04__nav" aria-label="Scenes">
<button class="plx-04__nav-dot is-active" aria-label="Scene 1" data-scene="0"></button>
<button class="plx-04__nav-dot" aria-label="Scene 2" data-scene="1"></button>
<button class="plx-04__nav-dot" aria-label="Scene 3" data-scene="2"></button>
<button class="plx-04__nav-dot" aria-label="Scene 4" data-scene="3"></button>
<button class="plx-04__nav-dot" aria-label="Scene 5" data-scene="4"></button>
</nav>
<!-- S1: Cosmos -->
<div class="plx-04__scene plx-04__s1">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.35"></div>
<div class="plx-04__fg">
<p class="plx-04__tag">CSS Parallax · Five Worlds</p>
<h1 class="plx-04__h1">The <em>infinite</em><br>canvas awaits</h1>
<p class="plx-04__p">Five worlds, each with its own sky. A full-page parallax journey — backgrounds drift at their own speed while you move through them.</p>
<div class="plx-04__accent"></div>
<div class="plx-04__scroll-hint">
<span>Scroll</span>
<div class="plx-04__arrow"></div>
</div>
</div>
</div>
</div>
<!-- S2: Desert -->
<div class="plx-04__scene plx-04__s2">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.4"></div>
<div class="plx-04__fg">
<p class="plx-04__num">02 / Desert</p>
<h2 class="plx-04__h">Heat &<br><em>Silence.</em></h2>
<p class="plx-04__p">The desert teaches patience. Vast emptiness between two points — just the relentless geometry of distance and will.</p>
</div>
</div>
</div>
<!-- S3: Ocean -->
<div class="plx-04__scene plx-04__s3">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.3"></div>
<div class="plx-04__fg">
<p class="plx-04__num">03 / Deep Ocean</p>
<h2 class="plx-04__h">Pressure<br>builds <em>diamonds.</em></h2>
<p class="plx-04__p">At depth, things become transparent. Clarity is not found in brightness — it's found in the dark, still water far below.</p>
</div>
</div>
</div>
<!-- S4: Forest -->
<div class="plx-04__scene plx-04__s4">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.45"></div>
<div class="plx-04__fg">
<p class="plx-04__num">04 / Ancient Forest</p>
<h2 class="plx-04__h"><em>Roots</em><br>remember<br>everything.</h2>
<p class="plx-04__p">The oldest trees have no memory of being seeds. They simply became what they were always going to be — slowly, in the green quiet.</p>
</div>
</div>
</div>
<!-- S5: Finale -->
<div class="plx-04__scene plx-04__s5">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.35"></div>
<div class="plx-04__fg">
<p class="plx-04__num">05 / Origin</p>
<h2 class="plx-04__h">Every end<br>is an <em>origin.</em></h2>
<p class="plx-04__p">The scroll ends. The journey doesn't. What you build with this moment is the only thing that matters now.</p>
</div>
</div>
</div>
</div> <div class="plx-04" id="plx04">
<nav class="plx-04__nav" aria-label="Scenes">
<button class="plx-04__nav-dot is-active" aria-label="Scene 1" data-scene="0"></button>
<button class="plx-04__nav-dot" aria-label="Scene 2" data-scene="1"></button>
<button class="plx-04__nav-dot" aria-label="Scene 3" data-scene="2"></button>
<button class="plx-04__nav-dot" aria-label="Scene 4" data-scene="3"></button>
<button class="plx-04__nav-dot" aria-label="Scene 5" data-scene="4"></button>
</nav>
<!-- S1: Cosmos -->
<div class="plx-04__scene plx-04__s1">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.35"></div>
<div class="plx-04__fg">
<p class="plx-04__tag">CSS Parallax · Five Worlds</p>
<h1 class="plx-04__h1">The <em>infinite</em><br>canvas awaits</h1>
<p class="plx-04__p">Five worlds, each with its own sky. A full-page parallax journey — backgrounds drift at their own speed while you move through them.</p>
<div class="plx-04__accent"></div>
<div class="plx-04__scroll-hint">
<span>Scroll</span>
<div class="plx-04__arrow"></div>
</div>
</div>
</div>
</div>
<!-- S2: Desert -->
<div class="plx-04__scene plx-04__s2">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.4"></div>
<div class="plx-04__fg">
<p class="plx-04__num">02 / Desert</p>
<h2 class="plx-04__h">Heat &<br><em>Silence.</em></h2>
<p class="plx-04__p">The desert teaches patience. Vast emptiness between two points — just the relentless geometry of distance and will.</p>
</div>
</div>
</div>
<!-- S3: Ocean -->
<div class="plx-04__scene plx-04__s3">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.3"></div>
<div class="plx-04__fg">
<p class="plx-04__num">03 / Deep Ocean</p>
<h2 class="plx-04__h">Pressure<br>builds <em>diamonds.</em></h2>
<p class="plx-04__p">At depth, things become transparent. Clarity is not found in brightness — it's found in the dark, still water far below.</p>
</div>
</div>
</div>
<!-- S4: Forest -->
<div class="plx-04__scene plx-04__s4">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.45"></div>
<div class="plx-04__fg">
<p class="plx-04__num">04 / Ancient Forest</p>
<h2 class="plx-04__h"><em>Roots</em><br>remember<br>everything.</h2>
<p class="plx-04__p">The oldest trees have no memory of being seeds. They simply became what they were always going to be — slowly, in the green quiet.</p>
</div>
</div>
</div>
<!-- S5: Finale -->
<div class="plx-04__scene plx-04__s5">
<div class="plx-04__sticky">
<div class="plx-04__bg" data-parallax="0.35"></div>
<div class="plx-04__fg">
<p class="plx-04__num">05 / Origin</p>
<h2 class="plx-04__h">Every end<br>is an <em>origin.</em></h2>
<p class="plx-04__p">The scroll ends. The journey doesn't. What you build with this moment is the only thing that matters now.</p>
</div>
</div>
</div>
</div>.plx-04, .plx-04 *, .plx-04 *::before, .plx-04 *::after {
box-sizing: border-box; margin: 0; padding: 0;
}
.plx-04 {
font-family: 'Outfit', sans-serif;
background: #04020f;
color: #f0ede8;
/* overflow:clip not overflow:hidden — see Demo 09 + 10 fixes
(commits 876bc78, this one). overflow:hidden creates a scroll
context that breaks position:sticky on descendants. */
overflow-x: clip;
}
/* Each section is a sticky-pinned scene: 200vh outer + 100vh
sticky inner = 100vh of pin runway during which the bg parallax
animation plays. Sticky pinning works correctly because the
root .plx-04 uses overflow-x:clip (not :hidden), which would
have broken the sticky containment chain. */
.plx-04__scene {
position: relative;
height: 200vh;
}
/* Each scene's outer container has a fallback bg color matching
its theme. Without these, the 100vh of "post-sticky-release"
scroll runway at the bottom of each scene's 200vh container
would show the body bg (#04020f), creating a dark band between
scenes as the visitor scrolls between them. The per-scene
theme colors below match the dominant midtone of each scene's
gradient bg, so the transition reads as one continuous scene
color instead of a dark stripe gap. */
.plx-04__s1 { background: #07041a; }
.plx-04__s2 { background: #3d1800; }
.plx-04__s3 { background: #002040; }
.plx-04__s4 { background: #071407; }
.plx-04__s5 { background: #0e0028; }
.plx-04__sticky {
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
}
/* Background layer — moves slower than scroll (parallax effect via JS) */
.plx-04__bg {
position: absolute;
inset: -25%;
will-change: transform;
}
/* Content — centered, normal scroll speed */
.plx-04__fg {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 40px;
z-index: 5;
}
/* ── Scene 1: Cosmos ── */
.plx-04__s1 .plx-04__bg {
background:
radial-gradient(ellipse at 20% 20%, rgba(120,60,200,0.5) 0%, transparent 45%),
radial-gradient(ellipse at 80% 75%, rgba(30,80,200,0.4) 0%, transparent 45%),
linear-gradient(135deg, #04020f 0%, #0a0520 100%);
}
.plx-04__s1 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background-image:
radial-gradient(1.5px 1.5px at 8% 12%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 25% 8%, rgba(255,255,255,0.8) 0%, transparent 100%),
radial-gradient(2px 2px at 45% 20%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 62% 6%, rgba(255,255,255,0.7) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 78% 18%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 12% 45%, rgba(255,255,255,0.6) 0%, transparent 100%),
radial-gradient(2px 2px at 55% 55%, rgba(255,255,255,0.9) 0%, transparent 100%),
radial-gradient(1px 1px at 35% 68%, rgba(255,255,255,0.7) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 88% 40%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 70% 72%, rgba(255,255,255,0.8) 0%, transparent 100%),
radial-gradient(1px 1px at 18% 82%, rgba(255,255,255,0.6) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 92% 85%, rgba(255,240,200,0.9) 0%, transparent 100%);
}
.plx-04__s1 .plx-04__bg::after {
content: '';
position: absolute;
width: 260px; height: 260px;
top: 18%; right: 20%;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #fff8e1 0%, #ffe082 60%);
box-shadow: 0 0 60px rgba(255,220,130,0.6), 0 0 140px rgba(255,180,80,0.25);
}
/* ── Scene 2: Desert ── */
.plx-04__s2 .plx-04__bg {
background:
radial-gradient(ellipse at 55% 30%, rgba(200,80,20,0.5) 0%, transparent 55%),
radial-gradient(ellipse at 20% 80%, rgba(140,60,0,0.3) 0%, transparent 45%),
linear-gradient(160deg, #1a0800 0%, #3d1800 40%, #6b2d00 100%);
}
.plx-04__s2 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background:
repeating-linear-gradient(45deg, transparent, transparent 40px, rgba(255,150,50,0.04) 40px, rgba(255,150,50,0.04) 41px),
repeating-linear-gradient(-45deg, transparent, transparent 80px, rgba(200,100,20,0.03) 80px, rgba(200,100,20,0.03) 81px);
}
.plx-04__s2 .plx-04__bg::after {
content: '';
position: absolute;
bottom: 15%;
left: -5%;
right: -5%;
height: 35%;
background: repeating-linear-gradient(
0deg,
rgba(180,80,20,0.08) 0px,
rgba(180,80,20,0.12) 4px,
transparent 4px,
transparent 18px
);
transform: skewY(-3deg);
}
/* ── Scene 3: Ocean ── */
.plx-04__s3 .plx-04__bg {
background:
radial-gradient(ellipse at 25% 55%, rgba(0,120,200,0.5) 0%, transparent 50%),
radial-gradient(ellipse at 75% 25%, rgba(0,200,160,0.25) 0%, transparent 45%),
linear-gradient(180deg, #001020 0%, #002040 55%, #003060 100%);
}
.plx-04__s3 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background:
repeating-linear-gradient(-30deg, transparent, transparent 50px, rgba(0,150,255,0.05) 50px, rgba(0,150,255,0.05) 52px);
}
.plx-04__s3 .plx-04__bg::after {
content: '';
position: absolute;
top: 40%;
left: -5%;
right: -5%;
height: 3px;
background: linear-gradient(90deg, transparent, rgba(0,200,180,0.4), transparent);
box-shadow: 0 20px 0 rgba(0,150,200,0.1), 0 -20px 0 rgba(0,150,200,0.1);
}
/* ── Scene 4: Forest ── */
.plx-04__s4 .plx-04__bg {
background:
radial-gradient(ellipse at 50% 0%, rgba(80,200,70,0.2) 0%, transparent 55%),
radial-gradient(ellipse at 20% 60%, rgba(40,120,40,0.2) 0%, transparent 45%),
linear-gradient(180deg, #020b02 0%, #071407 55%, #0a1e0a 100%);
}
.plx-04__s4 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background:
repeating-linear-gradient(90deg, transparent, transparent 80px, rgba(50,150,50,0.04) 80px, rgba(50,150,50,0.04) 82px),
repeating-linear-gradient(0deg, transparent, transparent 60px, rgba(30,100,30,0.04) 60px, rgba(30,100,30,0.04) 62px);
}
/* ── Scene 5: Finale ── */
.plx-04__s5 .plx-04__bg {
background:
radial-gradient(ellipse at 50% 45%, rgba(200,80,255,0.35) 0%, transparent 55%),
radial-gradient(ellipse at 20% 20%, rgba(80,120,255,0.25) 0%, transparent 45%),
linear-gradient(135deg, #08001a 0%, #180030 100%);
}
/* ── Typography ── */
.plx-04__tag {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.4em;
text-transform: uppercase;
color: rgba(255,255,255,0.35);
margin-bottom: 20px;
}
.plx-04__num {
font-family: 'Outfit', sans-serif;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.4em;
text-transform: uppercase;
color: rgba(255,255,255,0.3);
margin-bottom: 16px;
}
.plx-04__h1 {
font-family: 'Playfair Display', serif;
font-size: clamp(52px, 11vw, 140px);
font-weight: 700;
line-height: 0.9;
letter-spacing: -0.02em;
}
.plx-04__h1 em {
font-style: italic;
font-weight: 400;
background: linear-gradient(135deg, #fbbf24, #f59e0b);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.plx-04__h {
font-family: 'Playfair Display', serif;
font-size: clamp(42px, 8vw, 100px);
font-weight: 700;
line-height: 0.95;
}
.plx-04__s2 .plx-04__h em { color: #fb923c; font-style: italic; }
.plx-04__s3 .plx-04__h em { color: #38bdf8; font-style: italic; }
.plx-04__s4 .plx-04__h em { color: #86efac; font-style: italic; }
.plx-04__s5 .plx-04__h em { color: #e879f9; font-style: italic; }
.plx-04__p {
margin-top: 24px;
font-size: 16px;
font-weight: 300;
line-height: 1.75;
opacity: 0.5;
max-width: 480px;
}
.plx-04__scroll-hint {
position: absolute;
bottom: 36px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
letter-spacing: 0.25em;
text-transform: uppercase;
opacity: 0.35;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.plx-04__arrow {
width: 18px; height: 18px;
border-right: 1px solid rgba(255,255,255,0.4);
border-bottom: 1px solid rgba(255,255,255,0.4);
transform: rotate(45deg);
animation: plx-04-bounce 1.6s ease-in-out infinite;
}
@keyframes plx-04-bounce {
0%, 100% { transform: rotate(45deg) translateY(0); }
50% { transform: rotate(45deg) translateY(6px); }
}
/* Nav dots */
.plx-04__nav {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
z-index: 100;
display: flex;
flex-direction: column;
gap: 10px;
}
.plx-04__nav-dot {
display: block;
width: 6px; height: 6px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
border: none;
cursor: pointer;
transition: all 0.3s;
}
.plx-04__nav-dot.is-active {
background: rgba(255,255,255,0.85);
transform: scale(1.7);
}
/* Accent lines that appear at each scene */
.plx-04__accent {
width: 1px;
height: 60px;
background: linear-gradient(to bottom, rgba(255,255,255,0.5), transparent);
margin: 28px auto 0;
}
@media (max-width: 600px) {
.plx-04__nav { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.plx-04__bg { transform: none !important; }
.plx-04__arrow { animation: none; }
} .plx-04, .plx-04 *, .plx-04 *::before, .plx-04 *::after {
box-sizing: border-box; margin: 0; padding: 0;
}
.plx-04 {
font-family: 'Outfit', sans-serif;
background: #04020f;
color: #f0ede8;
/* overflow:clip not overflow:hidden — see Demo 09 + 10 fixes
(commits 876bc78, this one). overflow:hidden creates a scroll
context that breaks position:sticky on descendants. */
overflow-x: clip;
}
/* Each section is a sticky-pinned scene: 200vh outer + 100vh
sticky inner = 100vh of pin runway during which the bg parallax
animation plays. Sticky pinning works correctly because the
root .plx-04 uses overflow-x:clip (not :hidden), which would
have broken the sticky containment chain. */
.plx-04__scene {
position: relative;
height: 200vh;
}
/* Each scene's outer container has a fallback bg color matching
its theme. Without these, the 100vh of "post-sticky-release"
scroll runway at the bottom of each scene's 200vh container
would show the body bg (#04020f), creating a dark band between
scenes as the visitor scrolls between them. The per-scene
theme colors below match the dominant midtone of each scene's
gradient bg, so the transition reads as one continuous scene
color instead of a dark stripe gap. */
.plx-04__s1 { background: #07041a; }
.plx-04__s2 { background: #3d1800; }
.plx-04__s3 { background: #002040; }
.plx-04__s4 { background: #071407; }
.plx-04__s5 { background: #0e0028; }
.plx-04__sticky {
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
}
/* Background layer — moves slower than scroll (parallax effect via JS) */
.plx-04__bg {
position: absolute;
inset: -25%;
will-change: transform;
}
/* Content — centered, normal scroll speed */
.plx-04__fg {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 40px;
z-index: 5;
}
/* ── Scene 1: Cosmos ── */
.plx-04__s1 .plx-04__bg {
background:
radial-gradient(ellipse at 20% 20%, rgba(120,60,200,0.5) 0%, transparent 45%),
radial-gradient(ellipse at 80% 75%, rgba(30,80,200,0.4) 0%, transparent 45%),
linear-gradient(135deg, #04020f 0%, #0a0520 100%);
}
.plx-04__s1 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background-image:
radial-gradient(1.5px 1.5px at 8% 12%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 25% 8%, rgba(255,255,255,0.8) 0%, transparent 100%),
radial-gradient(2px 2px at 45% 20%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 62% 6%, rgba(255,255,255,0.7) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 78% 18%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 12% 45%, rgba(255,255,255,0.6) 0%, transparent 100%),
radial-gradient(2px 2px at 55% 55%, rgba(255,255,255,0.9) 0%, transparent 100%),
radial-gradient(1px 1px at 35% 68%, rgba(255,255,255,0.7) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 88% 40%, #fff 0%, transparent 100%),
radial-gradient(1px 1px at 70% 72%, rgba(255,255,255,0.8) 0%, transparent 100%),
radial-gradient(1px 1px at 18% 82%, rgba(255,255,255,0.6) 0%, transparent 100%),
radial-gradient(1.5px 1.5px at 92% 85%, rgba(255,240,200,0.9) 0%, transparent 100%);
}
.plx-04__s1 .plx-04__bg::after {
content: '';
position: absolute;
width: 260px; height: 260px;
top: 18%; right: 20%;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #fff8e1 0%, #ffe082 60%);
box-shadow: 0 0 60px rgba(255,220,130,0.6), 0 0 140px rgba(255,180,80,0.25);
}
/* ── Scene 2: Desert ── */
.plx-04__s2 .plx-04__bg {
background:
radial-gradient(ellipse at 55% 30%, rgba(200,80,20,0.5) 0%, transparent 55%),
radial-gradient(ellipse at 20% 80%, rgba(140,60,0,0.3) 0%, transparent 45%),
linear-gradient(160deg, #1a0800 0%, #3d1800 40%, #6b2d00 100%);
}
.plx-04__s2 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background:
repeating-linear-gradient(45deg, transparent, transparent 40px, rgba(255,150,50,0.04) 40px, rgba(255,150,50,0.04) 41px),
repeating-linear-gradient(-45deg, transparent, transparent 80px, rgba(200,100,20,0.03) 80px, rgba(200,100,20,0.03) 81px);
}
.plx-04__s2 .plx-04__bg::after {
content: '';
position: absolute;
bottom: 15%;
left: -5%;
right: -5%;
height: 35%;
background: repeating-linear-gradient(
0deg,
rgba(180,80,20,0.08) 0px,
rgba(180,80,20,0.12) 4px,
transparent 4px,
transparent 18px
);
transform: skewY(-3deg);
}
/* ── Scene 3: Ocean ── */
.plx-04__s3 .plx-04__bg {
background:
radial-gradient(ellipse at 25% 55%, rgba(0,120,200,0.5) 0%, transparent 50%),
radial-gradient(ellipse at 75% 25%, rgba(0,200,160,0.25) 0%, transparent 45%),
linear-gradient(180deg, #001020 0%, #002040 55%, #003060 100%);
}
.plx-04__s3 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background:
repeating-linear-gradient(-30deg, transparent, transparent 50px, rgba(0,150,255,0.05) 50px, rgba(0,150,255,0.05) 52px);
}
.plx-04__s3 .plx-04__bg::after {
content: '';
position: absolute;
top: 40%;
left: -5%;
right: -5%;
height: 3px;
background: linear-gradient(90deg, transparent, rgba(0,200,180,0.4), transparent);
box-shadow: 0 20px 0 rgba(0,150,200,0.1), 0 -20px 0 rgba(0,150,200,0.1);
}
/* ── Scene 4: Forest ── */
.plx-04__s4 .plx-04__bg {
background:
radial-gradient(ellipse at 50% 0%, rgba(80,200,70,0.2) 0%, transparent 55%),
radial-gradient(ellipse at 20% 60%, rgba(40,120,40,0.2) 0%, transparent 45%),
linear-gradient(180deg, #020b02 0%, #071407 55%, #0a1e0a 100%);
}
.plx-04__s4 .plx-04__bg::before {
content: '';
position: absolute;
inset: 0;
background:
repeating-linear-gradient(90deg, transparent, transparent 80px, rgba(50,150,50,0.04) 80px, rgba(50,150,50,0.04) 82px),
repeating-linear-gradient(0deg, transparent, transparent 60px, rgba(30,100,30,0.04) 60px, rgba(30,100,30,0.04) 62px);
}
/* ── Scene 5: Finale ── */
.plx-04__s5 .plx-04__bg {
background:
radial-gradient(ellipse at 50% 45%, rgba(200,80,255,0.35) 0%, transparent 55%),
radial-gradient(ellipse at 20% 20%, rgba(80,120,255,0.25) 0%, transparent 45%),
linear-gradient(135deg, #08001a 0%, #180030 100%);
}
/* ── Typography ── */
.plx-04__tag {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.4em;
text-transform: uppercase;
color: rgba(255,255,255,0.35);
margin-bottom: 20px;
}
.plx-04__num {
font-family: 'Outfit', sans-serif;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.4em;
text-transform: uppercase;
color: rgba(255,255,255,0.3);
margin-bottom: 16px;
}
.plx-04__h1 {
font-family: 'Playfair Display', serif;
font-size: clamp(52px, 11vw, 140px);
font-weight: 700;
line-height: 0.9;
letter-spacing: -0.02em;
}
.plx-04__h1 em {
font-style: italic;
font-weight: 400;
background: linear-gradient(135deg, #fbbf24, #f59e0b);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.plx-04__h {
font-family: 'Playfair Display', serif;
font-size: clamp(42px, 8vw, 100px);
font-weight: 700;
line-height: 0.95;
}
.plx-04__s2 .plx-04__h em { color: #fb923c; font-style: italic; }
.plx-04__s3 .plx-04__h em { color: #38bdf8; font-style: italic; }
.plx-04__s4 .plx-04__h em { color: #86efac; font-style: italic; }
.plx-04__s5 .plx-04__h em { color: #e879f9; font-style: italic; }
.plx-04__p {
margin-top: 24px;
font-size: 16px;
font-weight: 300;
line-height: 1.75;
opacity: 0.5;
max-width: 480px;
}
.plx-04__scroll-hint {
position: absolute;
bottom: 36px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
letter-spacing: 0.25em;
text-transform: uppercase;
opacity: 0.35;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.plx-04__arrow {
width: 18px; height: 18px;
border-right: 1px solid rgba(255,255,255,0.4);
border-bottom: 1px solid rgba(255,255,255,0.4);
transform: rotate(45deg);
animation: plx-04-bounce 1.6s ease-in-out infinite;
}
@keyframes plx-04-bounce {
0%, 100% { transform: rotate(45deg) translateY(0); }
50% { transform: rotate(45deg) translateY(6px); }
}
/* Nav dots */
.plx-04__nav {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
z-index: 100;
display: flex;
flex-direction: column;
gap: 10px;
}
.plx-04__nav-dot {
display: block;
width: 6px; height: 6px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
border: none;
cursor: pointer;
transition: all 0.3s;
}
.plx-04__nav-dot.is-active {
background: rgba(255,255,255,0.85);
transform: scale(1.7);
}
/* Accent lines that appear at each scene */
.plx-04__accent {
width: 1px;
height: 60px;
background: linear-gradient(to bottom, rgba(255,255,255,0.5), transparent);
margin: 28px auto 0;
}
@media (max-width: 600px) {
.plx-04__nav { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.plx-04__bg { transform: none !important; }
.plx-04__arrow { animation: none; }
}(() => {
const root = document.getElementById('plx04');
if (!root) return;
const bgs = Array.from(root.querySelectorAll('[data-parallax]')).map(el => ({
el,
speed: parseFloat(el.dataset.parallax),
sticky: el.closest('.plx-04__sticky'),
scene: el.closest('.plx-04__scene'),
}));
const dots = Array.from(root.querySelectorAll('.plx-04__nav-dot'));
const scenes = Array.from(root.querySelectorAll('.plx-04__scene'));
// Nav dot click — scroll to scene start
dots.forEach((dot, i) => {
dot.addEventListener('click', () => {
scenes[i]?.scrollIntoView({ behavior: 'smooth' });
});
});
let ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const sy = window.scrollY;
const vh = window.innerHeight;
// Parallax: bg moves slower than scroll
bgs.forEach(({ el, speed, scene }) => {
const rect = scene.getBoundingClientRect();
if (rect.bottom < -vh || rect.top > vh * 2) { ticking = false; return; }
// Progress within the scene's sticky section
const sceneTop = scene.offsetTop;
const progress = sy - sceneTop;
el.style.transform = `translateY(${progress * speed * -1}px)`;
});
// Active dot
let activeI = 0;
scenes.forEach((s, i) => {
const r = s.getBoundingClientRect();
if (r.top <= vh * 0.5) activeI = i;
});
dots.forEach((d, i) => d.classList.toggle('is-active', i === activeI));
ticking = false;
});
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})(); (() => {
const root = document.getElementById('plx04');
if (!root) return;
const bgs = Array.from(root.querySelectorAll('[data-parallax]')).map(el => ({
el,
speed: parseFloat(el.dataset.parallax),
sticky: el.closest('.plx-04__sticky'),
scene: el.closest('.plx-04__scene'),
}));
const dots = Array.from(root.querySelectorAll('.plx-04__nav-dot'));
const scenes = Array.from(root.querySelectorAll('.plx-04__scene'));
// Nav dot click — scroll to scene start
dots.forEach((dot, i) => {
dot.addEventListener('click', () => {
scenes[i]?.scrollIntoView({ behavior: 'smooth' });
});
});
let ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const sy = window.scrollY;
const vh = window.innerHeight;
// Parallax: bg moves slower than scroll
bgs.forEach(({ el, speed, scene }) => {
const rect = scene.getBoundingClientRect();
if (rect.bottom < -vh || rect.top > vh * 2) { ticking = false; return; }
// Progress within the scene's sticky section
const sceneTop = scene.offsetTop;
const progress = sy - sceneTop;
el.style.transform = `translateY(${progress * speed * -1}px)`;
});
// Active dot
let activeI = 0;
scenes.forEach((s, i) => {
const r = s.getBoundingClientRect();
if (r.top <= vh * 0.5) activeI = i;
});
dots.forEach((d, i) => d.classList.toggle('is-active', i === activeI));
ticking = false;
});
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})();More from 10 CSS Parallax Effects
CSS Parallax Image Grid / GalleryCSS Horizontal Parallax ScrollCSS Parallax Text Overlay EffectCSS Parallax Card Hover EffectCSS Zoom-In / Depth Parallax on ScrollCSS Parallax Background Blur TransitionCSS Parallax Hero SectionMulti-layered CSS Parallax Landscape / IllustrationCSS Sticky Parallax Sections
View the full collection →