Create Tik-Tok/Youtube Shorts like snap infinite scroll - React

Create Tik-Tok/Youtube Shorts like snap infinite scroll - React

Scroll snap is when you scroll a little and it auto scrolls to the next card in the list. You must have seen this feature on Instagram, youtube shorts and TikTok.

Snap Scroll can be achieved by CSS only. https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type

There are three ways to achieve this effect in react

  1. Vanilla CSS

  2. Styled Components

  3. React-hook

Browser Compatibility of scroll-snap-type is great. All the browsers have stable support for it.

Before you can define scroll snapping, you need to enable scrolling on a scroll container. You can do this by ensuring that the scroll container has a defined size and that it has overflow enabled.

You can then define scroll snapping on the scroll container by using the following two key properties:

Vanilla CSS

The scroll-snap-type CSS property sets how strictly snap points are enforced on the scroll container if there is one.

For the snap-scroll to work properly, we will have a Container and Children. the container will have the scroll-snap-type CSS property and the Children will have the scroll-snap-align CSS property.

scroll-snap-type: x mandatory;
scroll-snap-type: y proximity;

x and y will constrain the scroll-snap action to the x-axis and y-axis.

Mandatory means the scroll will always rest on the scroll-snap point( video component ).

Proximity is based on the proximity of the scroll-snap point/Children. Scroll container may come to rest on a snap point if it isn’t currently scrolled considering the user agent’s scroll parameters

Let’s start with simple HTML structure.


<!-- Scroll Container Component  -->
<div class="container" dir="ltr">
  <!-- List Component  -->
  <div id="list">
  </div>
  <!-- Loader Component  -->
  <p id="watch_end_of_document"  class="loader">
  Loader ...
</p>
</div>
.container {
   height: 100vh;
   scroll-snap-type: y mandatory;
   overflow: scroll;
}


.item {
  margin: 0;
  padding: 20px 0;
  text-align: center;
  scroll-snap-align: start;
  height: 300px;
}

.loader {
  height: 50px;
  display: flex;
  background: #eee;
  justify-content: center;
}


.item:nth-child(even) {
  background: #eee;
}

How does infinite scroll work?

Whenever the loader component comes into view, we run a fetch function.

// Get the Loader Component
const loader = document.getElementById("loader")

// addItems - can also be fetch function
function addItems() {
  const fragment = document.createDocumentFragment();

  for (let i = index + 1; i <= index + count; ++i) {
    const item = document.createElement("p");

    item.classList.add("item");
    item.textContent = `#${i}`;

    fragment.appendChild(item);
  }
  document.getElementById("list").appendChild(fragment);
  index += count;
}

// Using the IntersectionObserver API we will observe the loader component 
// if the 
const io = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    // componet is not in view we do nothing 
    if (!entry.isIntersecting) {
      return;
    }
    console.log('this is working');
    addItems();
  });
});

io.observe(loader);

%[https://codepen.io/pratiksharma15/pen/abRQXKj?editors=1111]

Styled Components

Let’s use Styled Components in React to do the same. Starting with the list Container Component which would have scroll-snap-type and overflow-y .

import styled from "styled-components";

const List = styled.div`
  max-height: 100vh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  background: #fff;
  display: flex;
  flex-direction: column;
  gap: 20px;
  scrollbar-width: none;

  &::-webkit-scrollbar {
    display: none; /* for Chrome, Safari, and Opera */
  }
`;

I used &::-webkit-scrollbar to hide the scrollbar.

Now, the Item component would have the scroll-snap-align CSS property.

const Item = styled.div`
  margin: 0;
  padding: 20px 0px;
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: center;
  align-content: center;
  scroll-snap-align: start;
  min-height: 70vh;

  background: #eee;
`;

Now, to we are done with the scroll-snap.

Infinite scroll with styled-component

  1. Create a Loader Component

  2. We are going to use the reack-hook-inview to observe if the loader is in the viewport

  3. If the Loader is in the viewport, fetch more items.

const Loader = styled.div`
  min-height: 20vh;
  margin-bottom: 30px;
  display: flex;
  background: #444;
  scroll-snap-align: start;
  color: #eee;
  align-content: center;
  align-items: center;
  scroll-snap-align: start;

  justify-content: center;
`;
function ScrollContainer() {
  const [state, setState] = useState([1, 2, 3, 4, 5]);
  return (
    <List>
      {state.map((el, index) => (
        <Item key={index + el}>{el} </Item>
      ))}
      <Loader >Loading...</Loader>
    </List>
  );
}
export default ScrollContainer;

react-hook-inview provides useInView hook, which we can use to observe the loader component.

We are going to use useEffect , if the Loader is in the viewport. We Fetch more data.

function ScrollContainer() {
... 

const [ref, isVisible] = useInView({
    threshold: 1
  });

  const newData = [...Array(10).keys()].map((x) => x + state.length + 1);

  useEffect(() => {
    if (isVisible) {
      setState((state) => [...state, ...newData]);
    }
  }, [isVisible]);

  return (
    <List>
      {state.map((el, index) => (
        <Item key={index + el}>{el} </Item>
      ))}
      <Loader ref={ref}>Loading...</Loader>
    </List>
  );
}

That’s it 🔥 . Here is CodeSandbox with all the code

%[https://codesandbox.io/s/infinite-scroll-snap-with-styled-components-nnmnom]

React Hooks

We are going to use react-use-scroll-snap . react-use-scroll-snap Give us simple API and keyboard accessibility as well. It uses the tweezer.js library.

import useScrollSnap from "react-use-scroll-snap";
import { useRef } from "react";

function ScrollComponent() {
  const scrollRef = useRef(null);
  const data = Array(10)
        .fill(1)
        .map((i, e) => e + 1);

  useScrollSnap({ ref: scrollRef, duration: 100, delay: 50 });
 

  return (
    <section className="container" ref={scrollRef}>
      {data.map((el, index) => (
        <div key={el} className="item">
          {el}
        </div>
      ))}
    </section>
  );
}

See, less code 🙌🏻. Here is the code Sandbox for you to fork it. Try to add a loader component, add a fetch function and useEffect to call the fetch function.

%[https://codesandbox.io/s/peaceful-glade-f3ndfw?file=/src/App.js]

Further Improvement:

  1. Add Auto-play with Intersection Observer API

Reference:

  1. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap/Basic_concepts

  2. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap

  3. https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type#try_it

Tue May 23 2023 13:36:49 GMT+0000 (Coordinated Universal Time)