CodyHouse Framework + Components are featured on Product Hunt! Join the discussion →

How to create a Sticky Hero section

In this tutorial, we'll take a look at how to create a sticky hero section, with the help of the CSS position sticky and the Intersection Observer API.

We recently published the Sticky hero component, a sticky section that reveals its content on scrolling.

In this article, we'll see how to recreate that effect. Heads-up: we won't be using the scroll event!

Let's do this! #

Here's a video tutorial explaining how to create the sticky hero effect. Feel free to skip the video if you prefer to read the article.

Join us on our YouTube channel for more web design tutorials!

The component we build in this tutorial is based on the CodyHouse framework.

👋 First time you hear about the CodyHouse Framework?

The basic idea is: we create a section composed of two main parts, a media element (e.g., an image) that we want to become fixed on scroll, and a content element that will scroll over the fixed media:

<section class="sticky-hero js-sticky-hero">
  <div class="sticky-hero__media" aria-hidden="true"></div>

  <div class="sticky-hero__content">
    <!-- content here -->
  </div>
</section>

To achieve this result, we can use the sticky value of the CSS position property and apply it to the media part of the .sticky-hero element:

.sticky-hero__media {
  position: sticky;
  top: 0;
  height: 100vh;
  background: url(../img/bg-img.jpg) no-repeat center center;
  background-size: cover;
}

With the above code, we are saying: as soon as the offset between the .sticky-hero__media and the viewport is zero (top: 0), make the element fixed.

Since its position is sticky (and not fixed), the element can only flow inside its container so it will move away if you keep scrolling.

That's it! Now as soon as the .sticky-hero__media reaches the top of the viewport, it becomes fixed, while the .sticky-hero__content keeps scrolling over it.

We can push this effect a little bit more: we can detect when the .sticky-hero__media becomes fixed and change its style (for example, create a smooth scale-down effect):

.sticky-hero--media-is-fixed .sticky-hero__media {
  transform: scale(0.9);
}

We'll need to use a little bit of Javascript for that.

As we do not need to know the exact value of the scrolling (we only need to know when the .sticky-hero__content starts intersecting the viewport), we'll be using the IntersectionObserver API rather than the scroll event.

We'll observe the .sticky-hero__content and when its intersection with the viewport is bigger than zero, we'll add the .sticky-hero--media-is-fixed class:

function initStickyHero(hero) {
  var observer = new IntersectionObserver(stickyCallback.bind(hero), {threshold: [0, 0.1, 1]});
  observer.observe(hero.content);
};

function stickyCallback(entries) {
  var bool = entries[0].intersectionRatio > 0;
  Util.toggleClass(this.element, 'sticky-hero--media-is-fixed', bool);
};

That's it!

Feedbacks/suggestions? Get in touch on Twitter.

Project duplicated.