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.
Setup
Run the following commands to set up a react app.
npx create-react-app smooth-scroll
cd smooth-scroll
yarn start
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
.parent{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
}
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
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;
Let's break it down.
- useWindowSize() is a custom hook that returns the current innerWidth and innerHeight of the window.
- scrollingContainerRef is used to apply translateY property on the div, on the fly.
-
data
is not a state because we don't want our react component re-rendering each time we scroll. - 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" - This useEffect runs only once and calls the
smoothScrolling
function. ThesmoothScrolling
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;
}
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;
}
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;
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;
}
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;
App.css
h2 {
text-align: center;
margin: 40px auto;
font-size: 4rem;
}
Thank you for reading!
Would love to hear your thought!
Latest comments (38)
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?
Hi Rahul, do you have any idea how to implement scroll-snap effect within this smooth scrolling effect? That would feel awesome! Thanks.
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?
In desktop it is only showing first section for me for my website
Also in mobile it is lagging alot while scrolling.
this is exactly what i am looking for! however.. do IntersectionObservers still work ?
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
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 ?
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!
Hey, I tried using it but it's not working , the contents inside
SmoothScroll
get fixed and no have not scrolling behaviourHave you got the solution?
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.
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.
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.scroll-behavior isn't what author tried to explain. for better understanding try to learn trendy Parallax effect.
This post is not about parallax scrolling. Your condescending tone is really not appropriate here.
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.
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.