isotropic-2022
Share
Blog
Search
Menu

How to Make Snapping Scroll Sections with CSS Scroll-Snap (fullpage.js alternative)

By James LePage
 on July 15, 2022

How to Make Snapping Scroll Sections with CSS Scroll-Snap (fullpage.js alternative)

By James LePage
 on July 15, 2022

In this tutorial, we're going to make a page that snaps from one fullscreen section to another as the user scrolls. This effect was popularized by the fullpage.js library. But now, there are better options due to licensing changes to fullpage (you now need to pay for it), newish CSS scroll-snap module, and complete browser support of the intersection observer API.

So, if you're looking for a fullpage.js alternative, you might not even need an entire library, you can just code it yourself!

In fact, the snapping effect is 100% CSS. JS is just used to fade elements in when the screen "snaps" into view.

We'll be making use of this "Dinosaur Park scroll snap demo" by Michelle Barker available on CodePen for inspiration:

https://codepen.io/michellebarker/pen/PowYKXJ See the Pen Dinosaur Park scroll snap demo by Michelle Barker (@michellebarker) on CodePen.

Heads up, you'll probably want to view this on a larger screen to fully experience the "scroll snap" effect.

By using CSS coupled with Intersection Observer, we'll make each fullscreen section of the page snap-scroll, and fade content in when it enters the viewport.

Even better, it's only ~30 lines of JS code! Animations are done with CSS; all we need to do is understand when sections and content enter our viewport with JS.

1) Build Your Page

First, will need to build a page, or configure an existing one to be compatible with this simple effect. There are two things to note here:

We're using a <main> element, which contains our sections. This main element is 100vh, as are each of the <section> elements within.

HTML:

<main> <section class="section section-1"> <div class="section__content" data-content >Content inside</div> </section> <section class="section section-2"> <div class="section__content" data-content >Content inside</div> </section> <section class="section section-3"> <header class="section__header"> <h3 class="section__title">Explore & Discover</h3> </header> <div class="section__content" data-content >Content inside</div> </section> </main>

CSS:

/* Scroll behaviour */ @media (min-height: 30em) { main { scroll-snap-type: y mandatory; height: 100vh; overflow-y: scroll; } } .section { color: white; position: relative; scroll-snap-align: center; display: flex; flex-direction: column; min-height: 100vh; } @media (min-height: 30em) { .section { height: 100vh; } }

2) Configure Snapping with CSS

image-16-11

Here we can see that there are multiple sections, each with a height of 100vh.

The <main> element, is also 100vh, with overflow-y:scroll.

To the user, scroll behavior is the same as a default webpage.

If you added the CSS mentioned above, the snapping effect will already be in play. Let's take a look at how it works.

First, the "@media (min-height: 30em)" query disables the snapping effect for mobile screen sizes that are to short in terms of height. Under the main section, there's one property that enables the snapping:

main { .... scroll-snap-type: y mandatory; .... }

This is a cool little property "sets how strictly snap points are enforced on the scroll container in case there is one" (MDN).

And then under our sections, we have:

.section { .... scroll-snap-align: center; .... }

This property "property specifies the box's snap position as an alignment of its snap area (as the alignment subject) within its snap container's snapport (as the alignment container)" (MDN).

Learn more about the CSS scroll-snap module here:

And it's as simple as that. Direct children within the <main> container (all sections), will scroll-snap to the center of the <main> container. Because both the Main and the Section are 100vh, the scrolling will snap from section to section.

3) Fade Content In Using Intersection Observer

By now, we have a full page scroll effect, and if you don't want to do anything else, you can skip this section and enjoy your website. However, when each full page section snaps into view, I want to fade-in the content within - a unique and cool effect (also one that's possible with fullpage.js).

To do this, we'll use intersection observer to understand the positioning of sections within our <main> viewport.

const sections = [...document.querySelectorAll("section")]; let options = { rootMargin: "0px", threshold: 0.75, }; const callback = (entries, observer) => { entries.forEach((entry) => { const { target } = entry; if (entry.intersectionRatio >= 0.75) { target.classList.add("is-visible"); } else { target.classList.remove("is-visible"); } }); }; const observer = new IntersectionObserver(callback, options); sections.forEach((section, index) => { const sectionChildren = [...section.querySelector("[data-content]").children]; sectionChildren.forEach((el, index) => { el.style.setProperty("--delay", `${index * 250}ms`); }); observer.observe(section); });

There are two things being done here. First, it identifies when a section is in the viewport, and as it enters, adds a CSS class .is-visible. As it leaves the viewport, it removes that class.

Additionally, as this section enters the viewport it consecutively (one after another) sets property of delay for 250ms on each of the children elements. This makes for a staggered fade in effect.

It only does this to children within elements that have the attribute of data-content. So you need to add that to the wrappers around your pages content if you want the fade in effect.

Now, to make this work, we need to set up the fade-in animation for elements that have the .is-visible class added.

@media (min-height: 30em) { .section__content > * { opacity: 0; transform: translate3d(0, 4rem, 0); transition: opacity 800ms var(--delay), transform 800ms cubic-bezier(0.13, 0.07, 0.26, 0.99) var(--delay); } } .is-visible .section__content > * { opacity: 1; transform: translate3d(0, 1rem, 0); } .is-visible .section__img { opacity: 0.75; }

If we refer back to the HTML from the beginning demo, you'll see that there's a div wrapping around content with the class of .section__content, and the data-content attribute.

<main> <section class="section section-1"> <div class="section__content" data-content >Content inside</div> </section> <section class="section section-2"> <div class="section__content" data-content >Content inside</div> </section> <section class="section section-3"> <header class="section__header"> <h3 class="section__title">Explore & Discover</h3> </header> <div class="section__content" data-content >Content inside</div> </section> </main>

This ties our JS and CSS together. Opacity is 0 for content until the .is-visible class is added by intersection observer when it enters the viewport. When it leaves, it reverts back to an opacity of 0, so regardless of your scrolling direction there will always be a feed in effect.

Finally, the delay which is applied consecutively to each child element staggers the fade in effect.

Note the third section. The section header isn't within a data-content section, so it won't fade in (will already be there).

All The Code

Here's the demo:

isotropic-2022-07-15-at-15-00-58

Here's all the code we used in our demo:

HTML

<main> <section class="section section-1"> <div class="section__content" data-content> <img class="section__img section__img--left" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/85648/trex.svg" alt="T-rex" /> <h2>Content inside</h2> <p>blah blah blah</p> </div> </section> <section class="section section-2"> <div class="section__content" data-content> <h2>Content inside</h2> <p>blah blah blah</p> </div> </section> <section class="section section-3"> <header class="section__header"> <h3 class="section__title">Explore & Discover</h3> </header> <div class="section__content" data-content> <h2>Content inside</h2> <p>blah blah blah</p> </div> </section> </main>

CSS

@media (min-height: 30em) { main { scroll-snap-type: y mandatory; height: 100vh; overflow-y: scroll; } } .section { color: white; position: relative; scroll-snap-align: center; display: flex; flex-direction: column; min-height: 100vh; } @media (min-height: 30em) { .section { height: 100vh; } } @media (min-height: 30em) { .section__content > * { opacity: 0; transform: translate3d(0, 4rem, 0); transition: opacity 800ms var(--delay), transform 800ms cubic-bezier(0.13, 0.07, 0.26, 0.99) var(--delay); } } .is-visible .section__content > * { opacity: 1; transform: translate3d(0, 1rem, 0); } .is-visible .section__img { opacity: 0.75; }

JS

const sections = [...document.querySelectorAll("section")]; let options = { rootMargin: "0px", threshold: 0.75, }; const callback = (entries, observer) => { entries.forEach((entry) => { const { target } = entry; if (entry.intersectionRatio >= 0.75) { target.classList.add("is-visible"); } else { target.classList.remove("is-visible"); } }); }; const observer = new IntersectionObserver(callback, options); sections.forEach((section, index) => { const sectionChildren = [...section.querySelector("[data-content]").children]; sectionChildren.forEach((el, index) => { el.style.setProperty("--delay", `${index * 250}ms`); }); observer.observe(section); });

Conclusion

This is the perfect fullpage.js alternative that doesn't just stop at snapping between full screen sections. We also used the intersection observer API to understand when a section enters the page and then dynamically apply a staggered animation. You can use this basic example to build off of and make some really cool effects with minimal vanilla JS and CSS.

Subscribe & Share
If you liked this content, subscribe for our monthly roundup of WordPress news, website inspiration, exclusive deals and interesting articles.
Unsubscribe at any time. We do not spam and will never sell or share your email.
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Article By
James LePage
Contributors/Editors
notloggedin
James LePage is the founder of Isotropic, a WordPress education company and digital agency. He is also the founder of CodeWP.ai, a venture backed startup bringing AI to WordPress creators.
We're looking for new authors. Explore Isotropic Jobs.
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram