10 CSS Parallax Effects 06 / 10
CSS Horizontal Parallax Scroll
Vertical scroll drives a five-panel horizontal track via requestAnimationFrame.
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-06">
<div class="plx-06__driver" id="plx06-driver">
<div class="plx-06__sticky">
<div class="plx-06__bar">
<span class="plx-06__logo">Horizontal·PLX</span>
<div class="plx-06__progress-track">
<div class="plx-06__progress-fill" id="plx06-fill"></div>
</div>
<span class="plx-06__counter" id="plx06-counter">01 / 05</span>
</div>
<div class="plx-06__viewport">
<div class="plx-06__track" id="plx06-track">
<!-- Panel 1 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">01</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="200" cy="200" r="180" stroke="#c084fc" stroke-width="1"/>
<circle cx="200" cy="200" r="120" stroke="#c084fc" stroke-width="0.5"/>
<circle cx="200" cy="200" r="60" stroke="#c084fc" stroke-width="0.5"/>
<line x1="20" y1="200" x2="380" y2="200" stroke="#c084fc" stroke-width="0.5"/>
<line x1="200" y1="20" x2="200" y2="380" stroke="#c084fc" stroke-width="0.5"/>
<line x1="73" y1="73" x2="327" y2="327" stroke="#c084fc" stroke-width="0.3"/>
<line x1="327" y1="73" x2="73" y2="327" stroke="#c084fc" stroke-width="0.3"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter One</span>
<h2 class="plx-06__panel-title">Motion<br>Through<br>Space</h2>
<p class="plx-06__panel-body">Vertical scroll drives horizontal travel. Each layer moves at its own speed — the background drifts, the number floats, the content holds firm. Depth from pure math.</p>
<span class="plx-06__panel-num">01 / 05</span>
</div>
<div class="plx-06__hint is-visible" id="plx06-hint">
<span>Scroll to travel</span>
<div class="plx-06__hint-arr">
<span></span><span></span><span></span>
</div>
</div>
</div>
<!-- Panel 2 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">02</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="40" y="40" width="320" height="320" stroke="#4ade80" stroke-width="1"/>
<rect x="100" y="100" width="200" height="200" stroke="#4ade80" stroke-width="0.5" transform="rotate(15,200,200)"/>
<rect x="140" y="140" width="120" height="120" stroke="#4ade80" stroke-width="0.5" transform="rotate(30,200,200)"/>
<line x1="40" y1="40" x2="360" y2="360" stroke="#4ade80" stroke-width="0.3"/>
<line x1="360" y1="40" x2="40" y2="360" stroke="#4ade80" stroke-width="0.3"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Two</span>
<h2 class="plx-06__panel-title">Layers<br>in<br>Tension</h2>
<p class="plx-06__panel-body">The background moves opposite to your scroll direction. The foreground barely shifts. The gap between them is where depth lives — created without a single CSS 3D property.</p>
<span class="plx-06__panel-num">02 / 05</span>
</div>
</div>
<!-- Panel 3 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">03</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<polygon points="200,30 370,310 30,310" stroke="#fb923c" stroke-width="1" fill="none"/>
<polygon points="200,80 320,290 80,290" stroke="#fb923c" stroke-width="0.5" fill="none"/>
<polygon points="200,130 270,270 130,270" stroke="#fb923c" stroke-width="0.5" fill="none"/>
<circle cx="200" cy="200" r="30" stroke="#fb923c" stroke-width="0.5"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Three</span>
<h2 class="plx-06__panel-title">Speed<br>as<br>Hierarchy</h2>
<p class="plx-06__panel-body">Fast-moving elements feel close. Slow-moving elements feel distant. The eye reads speed differentials as spatial distance — an ancient visual instinct made digital.</p>
<span class="plx-06__panel-num">03 / 05</span>
</div>
</div>
<!-- Panel 4 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">04</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="200" cy="200" rx="180" ry="80" stroke="#38bdf8" stroke-width="1"/>
<ellipse cx="200" cy="200" rx="130" ry="55" stroke="#38bdf8" stroke-width="0.5"/>
<ellipse cx="200" cy="200" rx="80" ry="30" stroke="#38bdf8" stroke-width="0.5"/>
<line x1="20" y1="200" x2="380" y2="200" stroke="#38bdf8" stroke-width="0.4"/>
<circle cx="200" cy="200" r="8" fill="#38bdf8" opacity="0.4"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Four</span>
<h2 class="plx-06__panel-title">Four<br>Kinds<br>of Still</h2>
<p class="plx-06__panel-body">Every layer is in motion — yet nothing feels chaotic. The geometry holds its orbit. The content waits. The number recedes. Stillness is a composition, not an absence.</p>
<span class="plx-06__panel-num">04 / 05</span>
</div>
</div>
<!-- Panel 5 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">05</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M 200 30 L 370 150 L 300 340 L 100 340 L 30 150 Z" stroke="#f472b6" stroke-width="1" fill="none"/>
<path d="M 200 80 L 320 175 L 265 320 L 135 320 L 80 175 Z" stroke="#f472b6" stroke-width="0.5" fill="none"/>
<circle cx="200" cy="200" r="40" stroke="#f472b6" stroke-width="0.5"/>
<circle cx="200" cy="200" r="8" fill="#f472b6" opacity="0.3"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Five</span>
<h2 class="plx-06__panel-title">The<br>End<br>Begins</h2>
<p class="plx-06__panel-body">Five panels, five velocities, one scroll gesture. The journey across them felt longer than the distance traveled — that's what parallax does. It stretches space with motion.</p>
<span class="plx-06__panel-num">05 / 05</span>
</div>
</div>
</div><!-- /track -->
<!-- dot nav -->
<div class="plx-06__dots" id="plx06-dots">
<div class="plx-06__dot is-active"></div>
<div class="plx-06__dot"></div>
<div class="plx-06__dot"></div>
<div class="plx-06__dot"></div>
<div class="plx-06__dot"></div>
</div>
</div><!-- /viewport -->
</div><!-- /sticky -->
</div><!-- /driver -->
</div> <div class="plx-06">
<div class="plx-06__driver" id="plx06-driver">
<div class="plx-06__sticky">
<div class="plx-06__bar">
<span class="plx-06__logo">Horizontal·PLX</span>
<div class="plx-06__progress-track">
<div class="plx-06__progress-fill" id="plx06-fill"></div>
</div>
<span class="plx-06__counter" id="plx06-counter">01 / 05</span>
</div>
<div class="plx-06__viewport">
<div class="plx-06__track" id="plx06-track">
<!-- Panel 1 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">01</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="200" cy="200" r="180" stroke="#c084fc" stroke-width="1"/>
<circle cx="200" cy="200" r="120" stroke="#c084fc" stroke-width="0.5"/>
<circle cx="200" cy="200" r="60" stroke="#c084fc" stroke-width="0.5"/>
<line x1="20" y1="200" x2="380" y2="200" stroke="#c084fc" stroke-width="0.5"/>
<line x1="200" y1="20" x2="200" y2="380" stroke="#c084fc" stroke-width="0.5"/>
<line x1="73" y1="73" x2="327" y2="327" stroke="#c084fc" stroke-width="0.3"/>
<line x1="327" y1="73" x2="73" y2="327" stroke="#c084fc" stroke-width="0.3"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter One</span>
<h2 class="plx-06__panel-title">Motion<br>Through<br>Space</h2>
<p class="plx-06__panel-body">Vertical scroll drives horizontal travel. Each layer moves at its own speed — the background drifts, the number floats, the content holds firm. Depth from pure math.</p>
<span class="plx-06__panel-num">01 / 05</span>
</div>
<div class="plx-06__hint is-visible" id="plx06-hint">
<span>Scroll to travel</span>
<div class="plx-06__hint-arr">
<span></span><span></span><span></span>
</div>
</div>
</div>
<!-- Panel 2 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">02</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="40" y="40" width="320" height="320" stroke="#4ade80" stroke-width="1"/>
<rect x="100" y="100" width="200" height="200" stroke="#4ade80" stroke-width="0.5" transform="rotate(15,200,200)"/>
<rect x="140" y="140" width="120" height="120" stroke="#4ade80" stroke-width="0.5" transform="rotate(30,200,200)"/>
<line x1="40" y1="40" x2="360" y2="360" stroke="#4ade80" stroke-width="0.3"/>
<line x1="360" y1="40" x2="40" y2="360" stroke="#4ade80" stroke-width="0.3"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Two</span>
<h2 class="plx-06__panel-title">Layers<br>in<br>Tension</h2>
<p class="plx-06__panel-body">The background moves opposite to your scroll direction. The foreground barely shifts. The gap between them is where depth lives — created without a single CSS 3D property.</p>
<span class="plx-06__panel-num">02 / 05</span>
</div>
</div>
<!-- Panel 3 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">03</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<polygon points="200,30 370,310 30,310" stroke="#fb923c" stroke-width="1" fill="none"/>
<polygon points="200,80 320,290 80,290" stroke="#fb923c" stroke-width="0.5" fill="none"/>
<polygon points="200,130 270,270 130,270" stroke="#fb923c" stroke-width="0.5" fill="none"/>
<circle cx="200" cy="200" r="30" stroke="#fb923c" stroke-width="0.5"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Three</span>
<h2 class="plx-06__panel-title">Speed<br>as<br>Hierarchy</h2>
<p class="plx-06__panel-body">Fast-moving elements feel close. Slow-moving elements feel distant. The eye reads speed differentials as spatial distance — an ancient visual instinct made digital.</p>
<span class="plx-06__panel-num">03 / 05</span>
</div>
</div>
<!-- Panel 4 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">04</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="200" cy="200" rx="180" ry="80" stroke="#38bdf8" stroke-width="1"/>
<ellipse cx="200" cy="200" rx="130" ry="55" stroke="#38bdf8" stroke-width="0.5"/>
<ellipse cx="200" cy="200" rx="80" ry="30" stroke="#38bdf8" stroke-width="0.5"/>
<line x1="20" y1="200" x2="380" y2="200" stroke="#38bdf8" stroke-width="0.4"/>
<circle cx="200" cy="200" r="8" fill="#38bdf8" opacity="0.4"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Four</span>
<h2 class="plx-06__panel-title">Four<br>Kinds<br>of Still</h2>
<p class="plx-06__panel-body">Every layer is in motion — yet nothing feels chaotic. The geometry holds its orbit. The content waits. The number recedes. Stillness is a composition, not an absence.</p>
<span class="plx-06__panel-num">04 / 05</span>
</div>
</div>
<!-- Panel 5 -->
<div class="plx-06__panel">
<div class="plx-06__bg" data-plx-bg="-0.3"></div>
<div class="plx-06__num-layer" data-plx-num="0.6">
<span class="plx-06__big-num">05</span>
</div>
<div class="plx-06__shape-layer" data-plx-shape="0.2">
<svg class="plx-06__shape-svg" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M 200 30 L 370 150 L 300 340 L 100 340 L 30 150 Z" stroke="#f472b6" stroke-width="1" fill="none"/>
<path d="M 200 80 L 320 175 L 265 320 L 135 320 L 80 175 Z" stroke="#f472b6" stroke-width="0.5" fill="none"/>
<circle cx="200" cy="200" r="40" stroke="#f472b6" stroke-width="0.5"/>
<circle cx="200" cy="200" r="8" fill="#f472b6" opacity="0.3"/>
</svg>
</div>
<div class="plx-06__content" data-plx-content="-0.1">
<span class="plx-06__panel-tag">Chapter Five</span>
<h2 class="plx-06__panel-title">The<br>End<br>Begins</h2>
<p class="plx-06__panel-body">Five panels, five velocities, one scroll gesture. The journey across them felt longer than the distance traveled — that's what parallax does. It stretches space with motion.</p>
<span class="plx-06__panel-num">05 / 05</span>
</div>
</div>
</div><!-- /track -->
<!-- dot nav -->
<div class="plx-06__dots" id="plx06-dots">
<div class="plx-06__dot is-active"></div>
<div class="plx-06__dot"></div>
<div class="plx-06__dot"></div>
<div class="plx-06__dot"></div>
<div class="plx-06__dot"></div>
</div>
</div><!-- /viewport -->
</div><!-- /sticky -->
</div><!-- /driver -->
</div>.plx-06, .plx-06 *, .plx-06 *::before, .plx-06 *::after {
box-sizing: border-box; margin: 0; padding: 0;
}
.plx-06 {
font-family: 'Space Grotesk', sans-serif;
background: #06060e;
color: #f0eff8;
}
/* Tall enough to drive 5 panels of horizontal scroll */
.plx-06__driver {
height: 500vh;
position: relative;
}
/* Sticky container holds the whole horizontal experience */
.plx-06__sticky {
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Top bar */
.plx-06__bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 40px;
flex-shrink: 0;
border-bottom: 1px solid rgba(240,239,248,0.08);
position: relative;
z-index: 10;
}
.plx-06__logo {
font-family: 'Space Mono', monospace;
font-size: 12px;
letter-spacing: 0.15em;
text-transform: uppercase;
opacity: 0.6;
}
.plx-06__progress-track {
flex: 1; max-width: 280px; margin: 0 32px;
height: 1px; background: rgba(255,255,255,0.1); position: relative;
}
.plx-06__progress-fill {
position: absolute; inset: 0 auto 0 0;
height: 100%; width: 0%; background: #fff; transition: width 60ms linear;
}
.plx-06__counter {
font-family: 'Space Mono', monospace;
font-size: 11px; opacity: 0.35; letter-spacing: 0.1em;
}
/* Horizontal viewport — clips the track */
.plx-06__viewport {
flex: 1;
position: relative;
overflow: hidden;
}
/* Track — moves horizontally, 5 panels wide */
.plx-06__track {
display: flex;
width: 500%;
height: 100%;
will-change: transform;
}
/* Each panel = 1/5 of the track = 100vw */
.plx-06__panel {
width: 20%;
height: 100%;
position: relative;
overflow: hidden;
flex-shrink: 0;
}
/* ── Layer A: Full-bleed background ── */
.plx-06__bg {
position: absolute;
inset: -15%;
will-change: transform;
transition: none;
}
.plx-06__panel:nth-child(1) .plx-06__bg { background: linear-gradient(135deg, #0a001a 0%, #1a0030 60%, #2a0050 100%); }
.plx-06__panel:nth-child(2) .plx-06__bg { background: linear-gradient(135deg, #001a10 0%, #00301a 60%, #004020 100%); }
.plx-06__panel:nth-child(3) .plx-06__bg { background: linear-gradient(135deg, #1a0800 0%, #30100000 0%, #301000 100%); }
.plx-06__panel:nth-child(3) .plx-06__bg { background: linear-gradient(135deg, #1a0a00 0%, #301200 100%); }
.plx-06__panel:nth-child(4) .plx-06__bg { background: linear-gradient(135deg, #00101a 0%, #001c30 100%); }
.plx-06__panel:nth-child(5) .plx-06__bg { background: linear-gradient(135deg, #1a001a 0%, #280028 100%); }
/* ── Layer B: Giant number ── */
.plx-06__num-layer {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
will-change: transform;
pointer-events: none;
}
.plx-06__big-num {
font-family: 'Space Mono', monospace;
font-size: clamp(180px, 35vw, 480px);
font-weight: 700;
line-height: 1;
letter-spacing: -0.05em;
opacity: 0.05;
color: #fff;
user-select: none;
}
/* ── Layer C: Decorative SVG shape ── */
.plx-06__shape-layer {
position: absolute;
inset: 0;
will-change: transform;
pointer-events: none;
}
.plx-06__shape-svg {
position: absolute;
top: 50%; right: 8%;
transform: translateY(-50%);
width: 36vw; height: 36vw;
max-width: 400px; max-height: 400px;
opacity: 0.18;
}
/* ── Layer D: Content ── */
.plx-06__content {
position: absolute;
top: 50%;
left: 10%;
transform: translateY(-50%);
z-index: 5;
will-change: transform;
max-width: 42%;
}
.plx-06__panel-tag {
font-family: 'Space Mono', monospace;
font-size: 10px;
letter-spacing: 0.3em;
text-transform: uppercase;
opacity: 0.4;
margin-bottom: 16px;
display: block;
}
.plx-06__panel-title {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(36px, 6vw, 84px);
font-weight: 700;
line-height: 0.95;
letter-spacing: -0.03em;
margin-bottom: 20px;
}
.plx-06__panel-body {
font-size: 14px;
font-weight: 300;
line-height: 1.75;
opacity: 0.5;
max-width: 340px;
}
.plx-06__panel-num {
display: block;
font-family: 'Space Mono', monospace;
font-size: 10px;
letter-spacing: 0.2em;
opacity: 0.25;
margin-top: 28px;
}
/* Accent colors per panel */
.plx-06__panel:nth-child(1) .plx-06__panel-title { color: #c084fc; }
.plx-06__panel:nth-child(2) .plx-06__panel-title { color: #4ade80; }
.plx-06__panel:nth-child(3) .plx-06__panel-title { color: #fb923c; }
.plx-06__panel:nth-child(4) .plx-06__panel-title { color: #38bdf8; }
.plx-06__panel:nth-child(5) .plx-06__panel-title { color: #f472b6; }
/* Vertical dot nav */
.plx-06__dots {
position: absolute;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
z-index: 20;
display: flex;
gap: 10px;
}
.plx-06__dot {
width: 5px; height: 5px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
transition: all 0.3s;
}
.plx-06__dot.is-active {
background: #fff;
transform: scale(1.6);
}
/* Scroll hint — only on panel 1 */
.plx-06__hint {
position: absolute;
right: 40px;
top: 50%;
transform: translateY(-50%);
z-index: 10;
display: flex;
align-items: center;
gap: 12px;
font-family: 'Space Mono', monospace;
font-size: 9px;
letter-spacing: 0.25em;
text-transform: uppercase;
opacity: 0;
transition: opacity 0.4s;
}
.plx-06__hint.is-visible { opacity: 0.35; }
.plx-06__hint-arr {
display: flex;
flex-direction: column;
gap: 4px;
}
.plx-06__hint-arr span {
display: block;
width: 18px;
height: 1px;
background: rgba(255,255,255,0.6);
animation: plx-06-arr 1.6s ease-in-out infinite;
}
.plx-06__hint-arr span:nth-child(2) { animation-delay: 0.15s; }
.plx-06__hint-arr span:nth-child(3) { animation-delay: 0.3s; }
@keyframes plx-06-arr {
0%, 100% { transform: scaleX(1); opacity: 0.6; }
50% { transform: scaleX(1.4); opacity: 1; }
}
@media (max-width: 768px) {
.plx-06__content { max-width: 70%; left: 6%; }
.plx-06__shape-svg { display: none; }
.plx-06__bar { padding: 14px 20px; }
.plx-06__progress-track { max-width: 140px; margin: 0 16px; }
}
@media (prefers-reduced-motion: reduce) {
.plx-06__bg, .plx-06__num-layer, .plx-06__shape-layer, .plx-06__content { transform: none !important; }
.plx-06__track { transform: none !important; }
.plx-06__hint-arr span { animation: none; }
} .plx-06, .plx-06 *, .plx-06 *::before, .plx-06 *::after {
box-sizing: border-box; margin: 0; padding: 0;
}
.plx-06 {
font-family: 'Space Grotesk', sans-serif;
background: #06060e;
color: #f0eff8;
}
/* Tall enough to drive 5 panels of horizontal scroll */
.plx-06__driver {
height: 500vh;
position: relative;
}
/* Sticky container holds the whole horizontal experience */
.plx-06__sticky {
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Top bar */
.plx-06__bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 40px;
flex-shrink: 0;
border-bottom: 1px solid rgba(240,239,248,0.08);
position: relative;
z-index: 10;
}
.plx-06__logo {
font-family: 'Space Mono', monospace;
font-size: 12px;
letter-spacing: 0.15em;
text-transform: uppercase;
opacity: 0.6;
}
.plx-06__progress-track {
flex: 1; max-width: 280px; margin: 0 32px;
height: 1px; background: rgba(255,255,255,0.1); position: relative;
}
.plx-06__progress-fill {
position: absolute; inset: 0 auto 0 0;
height: 100%; width: 0%; background: #fff; transition: width 60ms linear;
}
.plx-06__counter {
font-family: 'Space Mono', monospace;
font-size: 11px; opacity: 0.35; letter-spacing: 0.1em;
}
/* Horizontal viewport — clips the track */
.plx-06__viewport {
flex: 1;
position: relative;
overflow: hidden;
}
/* Track — moves horizontally, 5 panels wide */
.plx-06__track {
display: flex;
width: 500%;
height: 100%;
will-change: transform;
}
/* Each panel = 1/5 of the track = 100vw */
.plx-06__panel {
width: 20%;
height: 100%;
position: relative;
overflow: hidden;
flex-shrink: 0;
}
/* ── Layer A: Full-bleed background ── */
.plx-06__bg {
position: absolute;
inset: -15%;
will-change: transform;
transition: none;
}
.plx-06__panel:nth-child(1) .plx-06__bg { background: linear-gradient(135deg, #0a001a 0%, #1a0030 60%, #2a0050 100%); }
.plx-06__panel:nth-child(2) .plx-06__bg { background: linear-gradient(135deg, #001a10 0%, #00301a 60%, #004020 100%); }
.plx-06__panel:nth-child(3) .plx-06__bg { background: linear-gradient(135deg, #1a0800 0%, #30100000 0%, #301000 100%); }
.plx-06__panel:nth-child(3) .plx-06__bg { background: linear-gradient(135deg, #1a0a00 0%, #301200 100%); }
.plx-06__panel:nth-child(4) .plx-06__bg { background: linear-gradient(135deg, #00101a 0%, #001c30 100%); }
.plx-06__panel:nth-child(5) .plx-06__bg { background: linear-gradient(135deg, #1a001a 0%, #280028 100%); }
/* ── Layer B: Giant number ── */
.plx-06__num-layer {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
will-change: transform;
pointer-events: none;
}
.plx-06__big-num {
font-family: 'Space Mono', monospace;
font-size: clamp(180px, 35vw, 480px);
font-weight: 700;
line-height: 1;
letter-spacing: -0.05em;
opacity: 0.05;
color: #fff;
user-select: none;
}
/* ── Layer C: Decorative SVG shape ── */
.plx-06__shape-layer {
position: absolute;
inset: 0;
will-change: transform;
pointer-events: none;
}
.plx-06__shape-svg {
position: absolute;
top: 50%; right: 8%;
transform: translateY(-50%);
width: 36vw; height: 36vw;
max-width: 400px; max-height: 400px;
opacity: 0.18;
}
/* ── Layer D: Content ── */
.plx-06__content {
position: absolute;
top: 50%;
left: 10%;
transform: translateY(-50%);
z-index: 5;
will-change: transform;
max-width: 42%;
}
.plx-06__panel-tag {
font-family: 'Space Mono', monospace;
font-size: 10px;
letter-spacing: 0.3em;
text-transform: uppercase;
opacity: 0.4;
margin-bottom: 16px;
display: block;
}
.plx-06__panel-title {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(36px, 6vw, 84px);
font-weight: 700;
line-height: 0.95;
letter-spacing: -0.03em;
margin-bottom: 20px;
}
.plx-06__panel-body {
font-size: 14px;
font-weight: 300;
line-height: 1.75;
opacity: 0.5;
max-width: 340px;
}
.plx-06__panel-num {
display: block;
font-family: 'Space Mono', monospace;
font-size: 10px;
letter-spacing: 0.2em;
opacity: 0.25;
margin-top: 28px;
}
/* Accent colors per panel */
.plx-06__panel:nth-child(1) .plx-06__panel-title { color: #c084fc; }
.plx-06__panel:nth-child(2) .plx-06__panel-title { color: #4ade80; }
.plx-06__panel:nth-child(3) .plx-06__panel-title { color: #fb923c; }
.plx-06__panel:nth-child(4) .plx-06__panel-title { color: #38bdf8; }
.plx-06__panel:nth-child(5) .plx-06__panel-title { color: #f472b6; }
/* Vertical dot nav */
.plx-06__dots {
position: absolute;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
z-index: 20;
display: flex;
gap: 10px;
}
.plx-06__dot {
width: 5px; height: 5px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
transition: all 0.3s;
}
.plx-06__dot.is-active {
background: #fff;
transform: scale(1.6);
}
/* Scroll hint — only on panel 1 */
.plx-06__hint {
position: absolute;
right: 40px;
top: 50%;
transform: translateY(-50%);
z-index: 10;
display: flex;
align-items: center;
gap: 12px;
font-family: 'Space Mono', monospace;
font-size: 9px;
letter-spacing: 0.25em;
text-transform: uppercase;
opacity: 0;
transition: opacity 0.4s;
}
.plx-06__hint.is-visible { opacity: 0.35; }
.plx-06__hint-arr {
display: flex;
flex-direction: column;
gap: 4px;
}
.plx-06__hint-arr span {
display: block;
width: 18px;
height: 1px;
background: rgba(255,255,255,0.6);
animation: plx-06-arr 1.6s ease-in-out infinite;
}
.plx-06__hint-arr span:nth-child(2) { animation-delay: 0.15s; }
.plx-06__hint-arr span:nth-child(3) { animation-delay: 0.3s; }
@keyframes plx-06-arr {
0%, 100% { transform: scaleX(1); opacity: 0.6; }
50% { transform: scaleX(1.4); opacity: 1; }
}
@media (max-width: 768px) {
.plx-06__content { max-width: 70%; left: 6%; }
.plx-06__shape-svg { display: none; }
.plx-06__bar { padding: 14px 20px; }
.plx-06__progress-track { max-width: 140px; margin: 0 16px; }
}
@media (prefers-reduced-motion: reduce) {
.plx-06__bg, .plx-06__num-layer, .plx-06__shape-layer, .plx-06__content { transform: none !important; }
.plx-06__track { transform: none !important; }
.plx-06__hint-arr span { animation: none; }
}(() => {
const driver = document.getElementById('plx06-driver');
const track = document.getElementById('plx06-track');
const fill = document.getElementById('plx06-fill');
const counter = document.getElementById('plx06-counter');
const hint = document.getElementById('plx06-hint');
const dots = Array.from(document.querySelectorAll('.plx-06__dot'));
if (!driver || !track) return;
const PANELS = 5;
// Per-panel parallax layers
const panels = Array.from(track.querySelectorAll('.plx-06__panel')).map((panel, i) => ({
bg: panel.querySelector('[data-plx-bg]'),
num: panel.querySelector('[data-plx-num]'),
shape: panel.querySelector('[data-plx-shape]'),
content: panel.querySelector('[data-plx-content]'),
bgSpeed: parseFloat(panel.querySelector('[data-plx-bg]')?.dataset.plxBg || 0),
numSpeed: parseFloat(panel.querySelector('[data-plx-num]')?.dataset.plxNum || 0),
shapeSpeed: parseFloat(panel.querySelector('[data-plx-shape]')?.dataset.plxShape || 0),
contentSpeed: parseFloat(panel.querySelector('[data-plx-content]')?.dataset.plxContent || 0),
}));
let ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const driverRect = driver.getBoundingClientRect();
const driverH = driver.offsetHeight;
const vh = window.innerHeight;
const vw = window.innerWidth;
// scrollable range: driverH - vh
const scrollableH = driverH - vh;
const rawProgress = Math.max(0, Math.min(1, -driverRect.top / scrollableH));
// Horizontal offset of the track
const maxOffset = vw * (PANELS - 1);
const trackX = rawProgress * maxOffset;
track.style.transform = `translateX(${-trackX}px)`;
// Progress bar and counter
fill.style.width = (rawProgress * 100) + '%';
const activePanel = Math.min(PANELS - 1, Math.floor(rawProgress * PANELS));
counter.textContent = String(activePanel + 1).padStart(2,'0') + ' / 0' + PANELS;
dots.forEach((d, i) => d.classList.toggle('is-active', i === activePanel));
// Hide hint after first panel
if (hint) hint.classList.toggle('is-visible', rawProgress < 0.05);
// Per-layer parallax within each panel
// The local offset of each panel center relative to the viewport center
panels.forEach((p, i) => {
const panelCenter = i * vw; // absolute X center of panel in track
const viewCenter = trackX; // current left edge of visible area
const localOffset = panelCenter - viewCenter; // px from left edge
// "distance from center" — 0 when panel is fully visible
const fromCenter = localOffset - vw / 2;
if (p.bg) p.bg.style.transform = `translateX(${fromCenter * p.bgSpeed}px)`;
if (p.num) p.num.style.transform = `translateX(${fromCenter * p.numSpeed}px)`;
if (p.shape) p.shape.style.transform = `translateX(${fromCenter * p.shapeSpeed}px)`;
if (p.content) p.content.style.transform = `translateY(-50%) translateX(${fromCenter * p.contentSpeed}px)`;
});
ticking = false;
});
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})(); (() => {
const driver = document.getElementById('plx06-driver');
const track = document.getElementById('plx06-track');
const fill = document.getElementById('plx06-fill');
const counter = document.getElementById('plx06-counter');
const hint = document.getElementById('plx06-hint');
const dots = Array.from(document.querySelectorAll('.plx-06__dot'));
if (!driver || !track) return;
const PANELS = 5;
// Per-panel parallax layers
const panels = Array.from(track.querySelectorAll('.plx-06__panel')).map((panel, i) => ({
bg: panel.querySelector('[data-plx-bg]'),
num: panel.querySelector('[data-plx-num]'),
shape: panel.querySelector('[data-plx-shape]'),
content: panel.querySelector('[data-plx-content]'),
bgSpeed: parseFloat(panel.querySelector('[data-plx-bg]')?.dataset.plxBg || 0),
numSpeed: parseFloat(panel.querySelector('[data-plx-num]')?.dataset.plxNum || 0),
shapeSpeed: parseFloat(panel.querySelector('[data-plx-shape]')?.dataset.plxShape || 0),
contentSpeed: parseFloat(panel.querySelector('[data-plx-content]')?.dataset.plxContent || 0),
}));
let ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const driverRect = driver.getBoundingClientRect();
const driverH = driver.offsetHeight;
const vh = window.innerHeight;
const vw = window.innerWidth;
// scrollable range: driverH - vh
const scrollableH = driverH - vh;
const rawProgress = Math.max(0, Math.min(1, -driverRect.top / scrollableH));
// Horizontal offset of the track
const maxOffset = vw * (PANELS - 1);
const trackX = rawProgress * maxOffset;
track.style.transform = `translateX(${-trackX}px)`;
// Progress bar and counter
fill.style.width = (rawProgress * 100) + '%';
const activePanel = Math.min(PANELS - 1, Math.floor(rawProgress * PANELS));
counter.textContent = String(activePanel + 1).padStart(2,'0') + ' / 0' + PANELS;
dots.forEach((d, i) => d.classList.toggle('is-active', i === activePanel));
// Hide hint after first panel
if (hint) hint.classList.toggle('is-visible', rawProgress < 0.05);
// Per-layer parallax within each panel
// The local offset of each panel center relative to the viewport center
panels.forEach((p, i) => {
const panelCenter = i * vw; // absolute X center of panel in track
const viewCenter = trackX; // current left edge of visible area
const localOffset = panelCenter - viewCenter; // px from left edge
// "distance from center" — 0 when panel is fully visible
const fromCenter = localOffset - vw / 2;
if (p.bg) p.bg.style.transform = `translateX(${fromCenter * p.bgSpeed}px)`;
if (p.num) p.num.style.transform = `translateX(${fromCenter * p.numSpeed}px)`;
if (p.shape) p.shape.style.transform = `translateX(${fromCenter * p.shapeSpeed}px)`;
if (p.content) p.content.style.transform = `translateY(-50%) translateX(${fromCenter * p.contentSpeed}px)`;
});
ticking = false;
});
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})();More from 10 CSS Parallax Effects
CSS Parallax Background Blur TransitionCSS Parallax Hero SectionMulti-layered CSS Parallax Landscape / IllustrationCSS Sticky Parallax SectionsMulti-Scene Parallax ScrollingCSS Parallax Image Grid / GalleryCSS Parallax Text Overlay EffectCSS Parallax Card Hover EffectCSS Zoom-In / Depth Parallax on Scroll
View the full collection →