sections fade transition on scroll
by davidxmartins
HTML
<main id="stack">
<section class="panel">
<img src="https://cdn.slidemodel.com/wp-content/uploads/23212-01-8-step-arrows-circular-diagram-powerpoint-template-16x9-2.jpg" alt="Orange placeholder">
</section>
<section class="panel">
<img src="https://cdn.slidemodel.com/wp-content/uploads/23212-01-8-step-arrows-circular-diagram-powerpoint-template-16x9-4.jpg">
</section>
<section class="panel">
<img src="https://cdn.slidemodel.com/wp-content/uploads/23212-01-8-step-arrows-circular-diagram-powerpoint-template-16x9-8.jpg" alt="Blue placeholder">
</section>
</main>
CSS
html, body {
height: 100%;
margin: 0;
font-family: system-ui, sans-serif;
background: #000; /* prevents white flashes during fade */
}
main#stack {
position: relative;
height: 300svh; /* one full viewport height per section */
}
.panel {
position: fixed;
inset: 0;
opacity: 0;
transition: opacity 0.6s ease;
}
.panel img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Default first visible */
.panel:first-child {
opacity: 1;
z-index: 1;
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
.panel { transition: none; opacity: 1; }
}
JavaScript
(() => {
const panels = Array.from(document.querySelectorAll('.panel'));
const step = window.innerHeight; // each section is one viewport tall
window.addEventListener('scroll', () => {
const scroll = window.scrollY;
const index = Math.floor(scroll / step);
const progress = (scroll % step) / step; // 0 to 1 inside each section
panels.forEach((p, i) => {
let opacity = 0;
if (i === index) {
opacity = 1 - progress; // fade out current
} else if (i === index + 1) {
opacity = progress; // fade in next
}
p.style.opacity = opacity;
p.style.zIndex = i; // maintain order
});
});
})();