<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)
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.