DEV Community

Cover image for Slide stacking effect using position sticky
Vincent Humeau
Vincent Humeau

Posted on • Originally published at vinceumo.github.io

Slide stacking effect using position sticky

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>

Support

Position sticky has great support (Can I Use), 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;
    // ...
}

Support

CSS Scroll snap has good support (Can I Use), 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));

Support

The Intersection Observer API has good support (Can I Use), if you need to support older browsers you can use a polyfill.


Thanks for reading hope you enjoyed this small article.

Discussion (0)