<main class="scroll-container">
<section class="scroll-item is-visible">
<figure class="scroll-item__bg-image" data-img>
<img src='https://images.unsplash.com/photo-1485470733090-0aae1788d5af?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='mountain'>
</figure>
<h2>Scroll item 1</h2>
<div class="scroll-item__content">
<div class="scroll-item__inner" data-observer>
<span>Scroll me</span>
<span>↓</span>
</div>
</div>
</section>
<section class="scroll-item">
<h2>Scroll item 2</h2>
<div class="scroll-item__content">
<div class="scroll-item__inner" data-observer>
<span>Keep scrolling</span>
<span>↓</span>
</div>
</div>
</section>
<section class="scroll-item">
<figure class="scroll-item__bg-image" data-img>
<img src='https://images.unsplash.com/photo-1509515837298-2c67a3933321?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ' alt='night sky'>
</figure>
<h2>Scroll item 3</h2>
<div class="scroll-item__content">
<div class="scroll-item__inner" data-observer>
<span>I fade in and out on scroll</span>
<span>↓</span>
</div>
</div>
</section>
<section class="scroll-item">
<h2>Scroll item 4</h2>
<div class="scroll-item__content">
<div class="scroll-item__inner" data-observer>
<span>The end.</span>
</div>
</div>
</section>
</main>
@import url("https://fonts.googleapis.com/css?family=Montserrat:700");
* {
box-sizing: border-box;
}
$primary: #E040FB;
$secondary: #283593;
$dark: #212121;
body {
margin: 0;
font-family: Montserrat, sans-serif;
}
figure {
margin: 0;
}
.scroll-container {
height: 100vh;
overflow: scroll;
scroll-snap-type: y mandatory;
}
.scroll-item {
position: relative;
scroll-snap-align: start;
height: 100vh;
background-color: $dark;
&:nth-child(2) {
background-color: $primary;
}
&:nth-child(2n) {
h2 {
background-color: darken($secondary, 10%);
}
}
&:nth-child(3n) {
.scroll-item__bg-image img {
object-position: bottom;
}
}
h2 {
position: sticky;
top: 0;
margin: 0;
padding: 1rem;
background-color: $secondary;
color: lighten($secondary, 40%);
z-index: 1;
}
}
.scroll-item__content {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
height: 100vh;
color: white;
text-align: center;
font-size: 1.2rem;
span {
display: block;
}
}
.scroll-item__inner {
border: 2px solid white;
padding: 1rem;
transition: opacity 800ms, transform 800ms;
opacity: 0;
transform: translate3d(0, -1.5rem, 0);
&.is-visible {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.scroll-item__bg-image {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
&::after {
display: block;
content: '';
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.7);
transition: background 1000ms;
}
&.is-visible {
&::after {
background: rgba(0, 0, 0, 0.3);
}
}
}
View Compiled
const targets = document.querySelectorAll('[data-observer]')
const images = document.querySelectorAll('[data-img]')
const options = {
rootMargin: '0px',
threshold: 1.0
}
const addClass = (el) => {
if (!el.classList.contains('is-visible')) {
el.classList.add('is-visible')
}
}
const removeClass = (el) => {
if (el.classList.contains('is-visible')) {
el.classList.remove('is-visible')
}
}
const doThings = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
addClass(entry.target)
} else {
removeClass(entry.target)
}
})
}
const observer = new IntersectionObserver(doThings, options)
const observer2 = new IntersectionObserver(doThings, { ...options, threshold: 0.4 })
targets.forEach(target => {
observer.observe(target)
})
images.forEach(target => {
observer2.observe(target)
})
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.