DEV Community

Cover image for React: Super Simple Smooth Scrolling
Rahul
Rahul

Posted on • Updated on

React: Super Simple Smooth Scrolling

I have been wanting to redo my portfolio, and one of the major things I wanted it to have was smooth scrolling. So, I created a super simple smooth-scrolling effect with no extra dependencies but react.

In this blog, we'll be creating that together. So, let's get right into it.

Live Link

CodeSandBox

Github Repo

Setup

Run the following commands to set up a react app.

npx create-react-app smooth-scroll
cd smooth-scroll
yarn start
Enter fullscreen mode Exit fullscreen mode

Overview

So essentially what we are trying to do is to simply translate a div in Y-direction with a delay.
This div will hold the full SPA (Single Page Application), resulting in an all-out smooth scrolling effect.

<div className="parent">
  <div ref={scrollingContainer}>
    {/* The Complete App */}
  </div>
</div
Enter fullscreen mode Exit fullscreen mode
.parent{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  pointer-events: none;
}
Enter fullscreen mode Exit fullscreen mode

In this set up the div with the ref scrollingContainer will translate in Y-direction.
Notice that the div with a class of "parent" is set to position: fixed. This is essential otherwise the children div will just translate up leaving the space empty down below.

By doing this we are basically letting the browser know that our whole app is a fixed container of "width=100%" and "height=100%", with no scroll and stuff.

Later on, we'll be setting the height of the

tag equal to the "scrollingContainer div" and that will allow us to scroll.

On Scroll we'll translate the "scrollingContainer div".

Don't worry if this doesn't make sense. Hopefully, the code will make it clearer.

Final File Structure

File Structure

SmoothScroll.js

Create a file in src/components/SmoothScroll/SmoothScroll.js and paste the code below.
Don't worry about the imports just yet. We'll be creating them shortly.

import React, { useEffect, useRef } from "react";

import "./SmoothScroll.css";
import useWindowSize from "../../hooks/useWindowSize";

const SmoothScroll = ({ children }) => {
  // 1.
  const windowSize = useWindowSize();

  //2.
  const scrollingContainerRef = useRef();

  // 3.
  const data = {
    ease: 0.1,
    current: 0,
    previous: 0,
    rounded: 0,
  };

  // 4.
  useEffect(() => {
    setBodyHeight();
  }, [windowSize.height]);

  const setBodyHeight = () => {
    document.body.style.height = `${
      scrollingContainerRef.current.getBoundingClientRect().height
    }px`;
  };

  // 5.
  useEffect(() => {
    requestAnimationFrame(() => smoothScrollingHandler());
  }, []);

  const smoothScrollingHandler = () => {
    data.current = window.scrollY;
    data.previous += (data.current - data.previous) * data.ease;
    data.rounded = Math.round(data.previous * 100) / 100;

    scrollingContainerRef.current.style.transform = `translateY(-${data.previous}px)`;

    // Recursive call
    requestAnimationFrame(() => smoothScrollingHandler());
  };

  return (
    <div className="parent">
      <div ref={scrollingContainerRef}>{children}</div>
    </div>
  );
};

export default SmoothScroll;
Enter fullscreen mode Exit fullscreen mode

Let's break it down.

  1. useWindowSize() is a custom hook that returns the current innerWidth and innerHeight of the window.
  2. scrollingContainerRef is used to apply translateY property on the div, on the fly.
  3. data is not a state because we don't want our react component re-rendering each time we scroll.
  4. This useEffect runs only if the windowSize changes (if the user resizes the browser). setBodyHeight makes the height property on equal to the height of the "scrollingContainerRef div". After passing "position: fixed" to the "parent div", this makes sure that we have enough room to scroll through the whole "scrollingContainerRef div"
  5. This useEffect runs only once and calls the smoothScrolling function. The smoothScrolling function runs recursively changing the translate property on the "scrollingContainerRef div" whenever the user scroll.

Notice that we are calling the smoothScrolling function through requestAnimationFrame() function.

The window.requestAnimationFrame(**)** method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.

Note: Your callback routine must itself call requestAnimationFrame() again if you want to animate another frame at the next repaint. requestAnimationFrame() is 1 shot.

SmoothScrolling.css

Create a file in src/components/SmoothScroll/SmoothScroll.css and paste the code below.

.parent {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  pointer-events: none;
}
Enter fullscreen mode Exit fullscreen mode

useWindowSize.js

Create a file in src/hooks/useWindowSize.js and paste the code below

import { useState, useEffect } from "react";

export default function useWindowSize() {
  const getSize = () => {
    return {
      width: window.innerWidth,
      height: window.innerHeight,
    };
  };

  const [windowSize, setWindowSize] = useState(getSize);

  useEffect(() => {
    const handleResize = () => {
      setWindowSize(getSize());
    };

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return windowSize;
}
Enter fullscreen mode Exit fullscreen mode

This is a pretty straightforward hook that listens to the event of window resize and returns the latest innerWidth and innerHeight of the window.

Section.js

Create a file src/components/Section/Section.js and paste the code below.

import React from "react";

import "./section.css";

const section = ({ flexDirection }) => {
  return (
    <div className="section" style={{ flexDirection: flexDirection }}>
      <div className="left-container">
        <div className="block"></div>
      </div>

      <div className="right-container">
        <div className="container">
          <p>
            Lorem ipsum dolor sit amet consectetur adipisicing elit. In
            laudantium esse fugiat illum tempore sapiente soluta labore voluptas
            iusto deleniti ab suscipit dolores quisquam corrupti facilis, id
            temporibus mollitia repellat omnis tempora commodi eveniet.
            Incidunt, perspiciatis, adipisci laboriosam dolores quos dolor
            voluptate odio magnam aperiam, alias asperiores pariatur! Nisi,
            libero!
          </p>
        </div>
      </div>
    </div>
  );
};

export default section;
Enter fullscreen mode Exit fullscreen mode

SmoothScrolling.css

Create a file src/components/Section/Section.css and paste the code below.

.section {
  display: flex;
  justify-content: space-around;
  width: 100%;
  align-items: center;
  height: 100vh;
}

.block {
  width: 250px;
  height: 250px;
  padding: 60px;
  background-color: peachpuff;
}

.container {
  width: 500px;
}

p {
  font-size: 1.5rem;
}
Enter fullscreen mode Exit fullscreen mode

Just a react component to fill up some space in our scrolling Container

App.js

import React from "react";

import "./App.css";
import Section from "./components/Section/Section";
import SmoothScroll from "./components/SmoothScroll/SmoothScroll";

function App() {
  return (
    <SmoothScroll>
      <h2>Smooth Scrolling</h2>
      <Section flexDirection="row" />
      <Section flexDirection="row-reverse" />
      <Section flexDirection="row" />
      <Section flexDirection="row-reverse" />
      <Section flexDirection="row" />
      <Section flexDirection="row-reverse" />
    </SmoothScroll>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

App.css

h2 {
  text-align: center;
  margin: 40px auto;
  font-size: 4rem;
}
Enter fullscreen mode Exit fullscreen mode

Live Link

CodeSandBox

Github Repo

Thank you for reading!

Would love to hear your thought!

Top comments (37)

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

Personally cannot stand websites that do stuff like this. People expect websites to scroll as normal when they use the mousewheel etc. - it's very annoying when a site breaks this behaviour

Collapse
 
holdmypotion profile image
Rahul

I agree this could be annoying.
But each website serves a different purpose. It might irritate users of a commercial corporate website. But it could do wonders when used properly in portfolio websites or Product landing pages or maybe websites that provide a totally different experience than the old school websites.

I have seen a bunch of websites that implement this kind of scrolling and it feels beautiful.
Here, check them out :)
nahelmoussi.com/
boyntonyards.com/
jesperlandberg.dev/

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Sorry, I didn't make my point very clearly...

It can work where the interface is plainly not a 'normal' page - but rather a specialised interface that is being controlled using scrolling. If this is self evident from the page - the user will probably not have the expectation of scrolling working in the normal manner.

This type of interaction can indeed can be very effective. However, care should still be taken with the 'feel' of the scrolling - the first of the examples above is fine in this respect, but the others feel a little too elastic. They all look nice though.

In your own example, the page is plainly and clearly a 'normal' page. Breaking or screwing with basic website operation on such a page could well confuse the user or make them question what is going on since the page appears to be a normal one.

When using a site - the user should never be made to question or think about the interactions unnecessarily.

You should read Steve Krug's "Don't Make Me Think"

Thread Thread
 
holdmypotion profile image
Rahul

Gotcha!!
I absolutely agree. But the only reason why the example is a 'normal' page is that I wanted to save myself from writing CSS and stuff. The only purpose of this example is to showcase the smooth scrolling effect. I already stated in the 'Section.js' section, "Just a react component to fill up some space in our scrolling Container"

I agree with your point, BTW. And thanks for the recomendation.
I haven't read that book. I surely will!

Collapse
 
philipjc profile image
Philip J Cox

nahelmoussi.com/

Really is lovely design.

Collapse
 
andresausecha profile image
Andres Ausecha Mosquera • Edited

I personally do something similar in our website and works fine in my opinion. Regarding the mousewheel, a third of our users are not in desktop so you can't just consider a mouse

Collapse
 
ikirker profile image
Ian Kirker

I just tried the link on an iPad and it feels like the whole thing is attached to rubber bands. Let the browser smooth scrolling itself.

Collapse
 
bramus profile image
Bramus!

Or how about some CSS that takes care of all of this for you?

html {
  scroll-behavior: smooth;
}
Enter fullscreen mode Exit fullscreen mode

DONE. 😱

(Not supported in Safari though — Meh 😕)

Collapse
 
holdmypotion profile image
Rahul

Hey, Bramus! I don't think you can use "scroll-behavior: smooth" for something like this.
Here I created a pen for you
codepen.io/holdmypotion/pen/poNZzzo

I would love to know if there is a way. Do let me know :)

Collapse
 
bramus profile image
Bramus!

To scroll to a point in a page you need a link target. You can use <a name="foo" ></a> for this, or give your element an id <div id="foo"></div>. When now linking to #foo the page will scroll to said element.

Combined with scroll-behavior: smooth;, it will be scrolled smoothly.

Thread Thread
 
holdmypotion profile image
Rahul

Yea, I am quite aware of this use case. But can this be used to implement an overall smooth scrolling effect?
Like this: lic4s.csb.app/

Thread Thread
 
bramus profile image
Bramus!

Yes, because the browser provides that out-of-the-box 🥳

Thread Thread
 
jifakir profile image
JI Fakir

ha ha ha, you know nothing about html {
scroll-behavior: smooth;
}. But pretend as if you know everything. Visit the page author recommended you then compare with scroll-behavior. This is not the same thing. If you think it's possible as you calimed show us a demo. Before commenting on any topic understand the topic clearly.

Collapse
 
alecsmkt profile image
alejandro contreras

I want to do the same from click to and when scrolling is smooth, but I don't see that it works on the iphone

Collapse
 
alecsmkt profile image
alejandro contreras

hi, you can resolve this problem with safari?

Collapse
 
lukad profile image
Luka Dornhecker

I think this is an anti pattern. You should not mess with the default scroll behavior. On desktop I immediately leave sites that do this. And this example also makes scrolling on mobile feel very weird.
Sorry for being so negative but this is just my honest opinion.

Collapse
 
jifakir profile image
JI Fakir

This is a skill. He didn't force you to use everywhere. This skill has specific use case. So, what's the problem to learn? But I bet today or tomorrow you are gonna use this effect on your website if you are a developer. Because it doesn't a matter if you like or not, client demand is your work.

Collapse
 
lukad profile image
Luka Dornhecker • Edited

You make a lot of assumptions about my work... Learning is never a problem and no, no one forced me to use this. I stand by my original comment though. I believe this is an anti pattern and should not be implemented. If your goal is to programmatically and smoothly scroll to a specific element on a page then scroll-behavior: smooth; is your friend.

Thread Thread
 
jifakir profile image
JI Fakir

scroll-behavior isn't what author tried to explain. for better understanding try to learn trendy Parallax effect.

Thread Thread
 
lukad profile image
Luka Dornhecker

This post is not about parallax scrolling. Your condescending tone is really not appropriate here.

Thread Thread
 
jifakir profile image
JI Fakir • Edited

ha ha ha, did you visit his demo? It's a full page scrolling, not specific section. Did he explain about scroll-behavior property? Anyway, if it is possible what is in his demo with scroll-behaviour, why aren't you show us a demo. I know very well you will not be able to show. Because, it's not possible at this moment.

Thread Thread
 
lukad profile image
Luka Dornhecker

Yes, I checked out the demo. What is implemented there is exactly what I'm saying should be (and is by many) considered an anti pattern. I already gave you an example where scroll-behaviour: smooth; can be used. And luckily it does not behave like the demo. Anyways... you're being insanely rude. Considered yourself blocked. Hope you get better.

Collapse
 
matiishyn profile image
Ivan

I hate websites that try to change the way I scroll!!!
That's my browser, my mouse, my scrolling! Leave me alone :)

Collapse
 
jifakir profile image
JI Fakir

This is a skill. He didn't force you to use everywhere. This skill has specific use case. So, what's the problem to learn? But I bet today or tomorrow you are gonna use this effect on your website if you are a developer. Because it doesn't a matter if you like or not, client demand is your work.

Collapse
 
adrianswifter profile image
Adrian Swifter

I am getting the following error:
SmoothScroll.js:42 Uncaught TypeError: Cannot read properties of null (reading 'style')
at smoothScrollingHandler (SmoothScroll.js:42:1)
at SmoothScroll.js:44:1

Which is this line of code: scrollingContainerRef.current.style.transform = translateY(-${data.previous}px);

And when I console.log in smoothScrollingHandler I get infinite loop. Shouldn't this only work when we scroll?

Collapse
 
pmpmattia profile image
MattiaPompita

Wow, I like it so much very helpful!
I'm using this in my portfolio web, you know how can I use it in conjunction with an item attached to the right side of the screen? Because element with position: fixed; working only outside the SmoothScrollbar component.
Thank you, I loved your work

Collapse
 
mariusz1802 profile image
mariusz1802

I want to use this effect on multipage website. When I click button to next page I recive page with height from previous site. So, the next page is either too height or too small. How can I solve this problem ?

Collapse
 
shubham2924 profile image
shubham2924 • Edited

It's pretty amazing when I experience it on desktop but as soon as I switch on mobile devices to view the same scroll behavior, it is not user friendly I mean it gives some lag/latency while scrolling, if anyone has any solution over it please help :)
And also is there any way to disable this smooth scroll behavior for mobile devices and keep just for desktop?
Thanks in advance!

Collapse
 
bhargab profile image
BHARGAB KALITA

Hey, I tried using it but it's not working , the contents inside SmoothScroll get fixed and no have not scrolling behaviour

Collapse
 
vijaynavale profile image
Vijay Navale

Have you got the solution?

Collapse
 
kogans profile image
Stanislav Kogan

Smooth scrolling is laggy and resource-intensive. There is no place where it actually makes the experience better.

Collapse
 
gab profile image
Gabriel Magalhães dos Santos

Greeeat, I love smooth scroll effect, you could do a part two of this post showing how to implement parallax and this stuffs, it is really usefull

Collapse
 
holdmypotion profile image
Rahul

You caught me right on! 😃
I was planning on doing skew scrolling with some abstract backgrounds to implement parallax effect.

Collapse
 
rashidalikalwar profile image
Rashid Ali

Hi Rahul, do you have any idea how to implement scroll-snap effect within this smooth scrolling effect? That would feel awesome! Thanks.

Collapse
 
vijaynavale profile image
Vijay Navale

In desktop it is only showing first section for me for my website

Also in mobile it is lagging alot while scrolling.

Collapse
 
uxbycarol profile image
Carol hernandez

Hi This is very cool and works fine on my website but, what if I want some section (sections that are inside the smoothscroll componet) to get sticky to the top? how could I do It?

Collapse
 
koraysels profile image
Koray S.

this is exactly what i am looking for! however.. do IntersectionObservers still work ?