Scroll-driven CSS parallax
Each image drifts inside its frame as it crosses the screen, using animation-timeline view() and no script at all.
CSSIntermediate
<p class="hint">Scroll inside this frame</p>
<div class="col">
<div class="frame"><div class="img i1"></div></div>
<div class="frame"><div class="img i2"></div></div>
<div class="frame"><div class="img i3"></div></div>
<div class="frame"><div class="img i1"></div></div>
</div>
body{display:block;height:auto;padding:30px 0 80px}
.col{width:min(440px,88vw);margin:0 auto;display:grid;gap:36px}
.hint{text-align:center;color:#a6938f;font-size:14px}
.frame{height:230px;overflow:hidden;border-radius:16px}
.img{height:150%;width:100%;margin-top:-10%}
.i1{background:radial-gradient(circle at 70% 30%,#ffd166 0 8%,transparent 9%),linear-gradient(170deg,#e5603a,#8a3a22 70%)}
.i2{background:radial-gradient(circle at 30% 60%,#f08a5d 0 7%,transparent 8%),linear-gradient(170deg,#6e2a18,#2a0f0b)}
.i3{background:radial-gradient(circle at 60% 40%,#fff 0 6%,transparent 7%),linear-gradient(170deg,#d98a8a,#4e1408)}
@supports (animation-timeline:view()){
.img{animation:drift linear both;animation-timeline:view()}
@keyframes drift{from{transform:translateY(-14%)}to{transform:translateY(14%)}}
}
@media (prefers-reduced-motion:reduce){.img{animation:none}}
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Scroll-driven CSS parallax</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{display:grid;place-items:center;background:#140807;color:#fffffc;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
body{display:block;place-items:normal;height:auto;padding:30px 0 80px}
.col{width:min(440px,88vw);margin:0 auto;display:grid;gap:36px}
.hint{text-align:center;color:#a6938f;font-size:14px}
.frame{height:230px;overflow:hidden;border-radius:16px}
.img{height:150%;width:100%;margin-top:-10%}
.i1{background:radial-gradient(circle at 70% 30%,#ffd166 0 8%,transparent 9%),linear-gradient(170deg,#e5603a,#8a3a22 70%)}
.i2{background:radial-gradient(circle at 30% 60%,#f08a5d 0 7%,transparent 8%),linear-gradient(170deg,#6e2a18,#2a0f0b)}
.i3{background:radial-gradient(circle at 60% 40%,#fff 0 6%,transparent 7%),linear-gradient(170deg,#d98a8a,#4e1408)}
@supports (animation-timeline:view()){
.img{animation:drift linear both;animation-timeline:view()}
@keyframes drift{from{transform:translateY(-14%)}to{transform:translateY(14%)}}
}
@media (prefers-reduced-motion:reduce){.img{animation:none}}
</style></head>
<body>
<p class="hint">Scroll inside this frame</p>
<div class="col">
<div class="frame"><div class="img i1"></div></div>
<div class="frame"><div class="img i2"></div></div>
<div class="frame"><div class="img i3"></div></div>
<div class="frame"><div class="img i1"></div></div>
</div>
</body></html>
Good for
Modern, silky image parallax with zero JavaScript and the best possible performance.
Watch out for
Chrome, Edge and Safari 26+. Outside @supports the images simply sit still, which is a fine fallback.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.