DEV Community

Krypton | Madhusudan Babar
Krypton | Madhusudan Babar

Posted on

How to implement a scroll snap with intersection observer

I am inspired by Rolls Royce website and want to implement the same scroll snapping feature in mine as well, I did it with the HTML default scroll-snap-type but which gives me expected behavior but creates two scrollbars, one for the container and another one for the body, which is not expected so I tried to go with the IntersectionObserver but it causes an issue, I can travel to only adjacent slide when directly jumping from 1st slide to 3rd slide.

Here is the code sandbox link: https://codesandbox.io/s/scrollsnap-forked-pre0c?file=/pages/index.vue

I am inspired by Rolls Royce website and want to implement the same scroll snapping feature in mine as well, I did it with the HTML default scroll-snap-type which gives me expected behavior but creates two scrollbars, one for the container and another one for the body, which is not…

Here is the code that I am working:

<template>
  <main class="landing">
    <nav class="scroller">
      <ul class="scroller__list">
        <li
          class="scroller__item"
          v-for="(slide, index) in slides"
          :key="index"
          @click.prevent="scroll(slide.id)"
        >
          <a
            class="scroller__dot"
            :href="'#' + slide.id"
            @click.prevent="scroll(slide.id)"
          ></a>
        </li>
      </ul>
    </nav>
    <div class="slides-container">
      <slide
        class="slide"
        v-for="(slide, index) in slides"
        :key="index"
        :img="slide.img"
        :id="slide.id"
        :format="slide.format"
        :filter="slide.filter"
        >{{ slide.content }}</slide
      >
    </div>
  </main>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  data() {
    return {
      slides: [
        {
          img: "car-slide-1.png",
          content: "hello world",
          id: "car-slide-1",
          filter: "color-burn",
        },
        {
          img: "car-slide-2.png",
          content: "Second Car",
          id: "car-slide-2",
          filter: "color-burn",
        },
        {
          img: "car-slide-3.png",
          content: "Third slide",
          id: "car-slide-3",
          filter: "color-burn",
        },
      ],
      observer: null as any as IntersectionObserver,
      options: {
        threshold: [0.5],
        root: process.browser
          ? document.getElementsByClassName("slides-container")[0]
          : null,
      } as IntersectionObserverInit,
    };
  },
  methods: {
    scroll(id: string, who: string | null = null) {
      console.log("scrolling to ", id, who ? "by " + who : "");
      document.getElementById(id)?.scrollIntoView({
        behavior: "smooth",
        block: "start",
      });
    },
  },
  mounted() {
    let scrolling = false;
    this.observer = new IntersectionObserver((entries, observer) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting && !scrolling) {
          let top = entry.boundingClientRect.top;
          scrolling = true;
          window.scroll({ behavior: "smooth", top: window.pageYOffset + top });
        }
        scrolling = false;
      });
    }, this.options);
    document
      .querySelectorAll(".slide")
      .forEach((slide) => this.observer.observe(slide));
  },
});
</script>
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
phaberest profile image
Luca Stefano Sartori

I am trying to implement the same, but I'm afraid your codesandbox is having the same issue I'm experiencing in which the observer is not properly "observing" and thus doesn't highlight the current slide