Web Pages Medium
Hero Parallax
A multi-layer parallax hero section driven by scroll position, creating depth through independent layer speeds.
cssjstransformscroll-event
Targets: TS JS HTML React
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #030712;
color: #f1f5f9;
overflow-x: hidden;
}
/* --- Hero --- */
.hero {
position: relative;
height: 100vh;
overflow: hidden;
display: grid;
place-items: center;
}
/* Parallax layer */
.layer {
position: absolute;
inset: -20%;
will-change: transform;
}
/* Stars layer */
.stars {
width: 100%;
height: 100%;
background-image:
radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.8) 0%, transparent 100%),
radial-gradient(1px 1px at 60% 15%, rgba(255,255,255,0.6) 0%, transparent 100%),
radial-gradient(1px 1px at 80% 60%, rgba(255,255,255,0.7) 0%, transparent 100%),
radial-gradient(2px 2px at 40% 75%, rgba(255,255,255,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.8) 0%, transparent 100%),
radial-gradient(1px 1px at 10% 55%, rgba(255,255,255,0.6) 0%, transparent 100%),
radial-gradient(2px 2px at 50% 88%, rgba(255,255,255,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 90% 25%, rgba(255,255,255,0.7) 0%, transparent 100%);
background-size: 500px 400px;
background-repeat: repeat;
}
/* Gradient orbs */
.orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
}
.orb--1 {
width: 500px;
height: 400px;
top: 10%;
left: 5%;
background: rgba(56, 189, 248, 0.2);
}
.orb--2 {
width: 450px;
height: 380px;
bottom: 15%;
right: 5%;
background: rgba(168, 85, 247, 0.2);
}
/* Content layer — centered */
.layer--content {
position: relative;
inset: auto;
width: 100%;
display: grid;
place-items: center;
z-index: 10;
}
.hero-content {
max-width: 720px;
text-align: center;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.25rem;
}
.hero-eyebrow {
font-size: 0.8125rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #38bdf8;
}
.hero-title {
font-size: clamp(2.5rem, 7vw, 5rem);
font-weight: 800;
line-height: 1.1;
letter-spacing: -0.03em;
background: linear-gradient(160deg, #ffffff 40%, #94a3b8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: clamp(1rem, 2vw, 1.125rem);
line-height: 1.65;
color: #64748b;
max-width: 520px;
}
.hero-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
margin-top: 0.5rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.9375rem;
font-weight: 600;
padding: 0.75rem 1.75rem;
border-radius: 0.875rem;
text-decoration: none;
transition:
background 0.2s ease,
box-shadow 0.2s ease,
transform 0.15s ease;
}
.btn--primary {
background: #0ea5e9;
color: #ffffff;
box-shadow: 0 0 24px rgba(14, 165, 233, 0.4);
}
.btn--primary:hover {
background: #38bdf8;
box-shadow: 0 0 32px rgba(14, 165, 233, 0.6);
transform: translateY(-1px);
}
.btn--ghost {
background: rgba(255, 255, 255, 0.06);
color: #cbd5e1;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.btn--ghost:hover {
background: rgba(255, 255, 255, 0.1);
color: #f1f5f9;
transform: translateY(-1px);
}
/* Below fold */
.below-fold {
min-height: 60vh;
display: grid;
place-items: center;
color: #475569;
font-size: 1.125rem;
}
/* Disable parallax for reduced motion */
@media (prefers-reduced-motion: reduce) {
.layer {
position: relative;
inset: auto;
transform: none !important;
}
}Hero Parallax
A multi-layer parallax hero that creates a sense of depth by moving background layers at different speeds relative to scroll position.
How it works
Each layer has a data-speed attribute controlling how fast it moves relative to the scroll:
0.0— fixed (doesn’t move)0.5— moves at half scroll speed1.0— moves at full scroll speed (standard)
On scroll, requestAnimationFrame reads window.scrollY and applies translateY to each layer.
Performance tips
- Use
will-change: transformto hint GPU compositing - Keep layers to 3–4 max to avoid overdraw
- Use
requestAnimationFrame(notscrollevent directly) for smooth 60fps
Accessibility
Wrap with prefers-reduced-motion check to disable parallax for users who prefer it.