Demo gif

Recently I was looking to create a slide (card) stacking effect on scroll. The closest resource I could find about this effect was Card Stack: Scroll Effect by Sanjeev Yadav. I wanted to look if I could recreate a similar effect only with CSS.

Basic effect with position sticky

Position sticky enables to position an element like position fixed relative to its parent until it reaches the boundary of the parent.

Sticky positioning can be thought of as a hybrid of relative and fixed positioning. A stickily positioned element is treated as relatively positioned until it crosses a specified threshold, at which point it is treated as fixed until it reaches the boundary of its parent.

MDN - Sticky positioning

We can use position sticky to stick each slide, at the top at the parent and have a fixe height.

CSS:

.stacking-slide {
    height: 100vh;
    width: 100%;
    position: sticky;
    top: 0;
  
//   Not needed if 100vh
//   &:nth-last-child(1) {
//      height: 100vh;
//   }
}

HTML:

<section class="stacking-slide">
    <h2>Section 1</h2>
</section>
<section class="stacking-slide">
    <h2>Section 2</h2>
</section>
<section class="stacking-slide">
    <h2>Section 3</h2>
</section>
<section class="stacking-slide">
    <h2>Section 4</h2>
</section>
<section class="stacking-slide">
    <h2>Section 5</h2>
</section>

See the Pen Slide Stacking Effect by Vincent Humeau (@vinceumo) on CodePen.

Support

Can I Use css-sticky? Data on support for the css-sticky feature across the major browsers from caniuse.com.

Position sticky has great support, but if you need to support older browsers you can use a polyfill.

Vertical Scroll Snap

CSS scroll snap permits us to make each slide position nicely at the top of the viewport after scroll.

CSS Scroll Snap is a module of CSS that introduces scroll snap positions, which enforce the scroll positions that a scroll containerโ€™s scrollport may end at after a scrolling operation has completed.

MDN - CSS Scroll Snap

To use scroll snap we are going to wrap our sections into .vertical-scroll-snap. This element is going to have a fixe height of 100vh and overflow-y: scroll so the user can scroll between sections.

Next step we are going to look into scroll snap. First we are going the set scroll-snap-type (MDN). This property defines how strictly snap points are enforced on scroll. We are going to set it to y mandatory. y is indicating that the snap positions is in its vertical axis only. mandatory that the scroll container will rest on a snap point of the section if it isn’t currently scrolled.

Then we need to tell our sections which part need to be aligned to the container. We are going to use scroll-snap-align (CSS Tricks) with the value start.

HTML:

<div class="vertical-scroll-snap">
  <section class="stacking-slide">
    <h2>Section 1</h2>
  </section>
  <!-- ... -->
</div>

SCSS:

.vertical-scroll-snap {
    overflow-y: scroll;
    scroll-snap-type: y mandatory;
    max-height: 100vh;
}

.stacking-slide {
    scroll-snap-align: start;
    // ...
}

See the Pen Vertical Scroll Snap by Vincent Humeau (@vinceumo) on CodePen.

Support

Can I Use css-snappoints? Data on support for the css-snappoints feature across the major browsers from caniuse.com.

CSS Scroll snap has good support, if you need to support older browsers you can use a polyfill.

Add some nice transition using Intersection Observer API.

The Intersection Observer API, lets us to detect when an element enters the viewport. We can trigger a callback function when this element enters or leave the viewport.

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.

MDN - Intersection Observer API

In the following example, we are adding the class .is-intersecting when a .stacking-slide enter 10% of the viewport. And we remove this class when the slide leaves it. We can now add some nice transitions using CSS.

JS:

const sectionEls = document.querySelectorAll(".stacking-slide");

const options = {
  rootMargin: "-10% 0% -10% 0%"
};

const observer = new IntersectionObserver(entries => {
  entries.forEach(function(entry) {
    if (entry.isIntersecting) {
      entry.target.classList.add("is-intersecting");
    } else {
      entry.target.classList.remove("is-intersecting");
    }
  });
}, options);

sectionEls.forEach(el => observer.observe(el));

See the Pen Intersection Observer by Vincent Humeau (@vinceumo) on CodePen.

Support

Can I Use intersectionobserver? Data on support for the intersectionobserver feature across the major browsers from caniuse.com.

The Intersection Observer API has good support, if you need to support older browsers you can use a polyfill.


Thanks for reading hope you enjoyed this small article.