Making a smooth scroll with pure CSS and JS
See this and many other articles at lucaspaganini.com
Do you want a smooth scroll? Forget JQuery, we're past that. Let me introduce you to our tools for a native smooth scroll.
CSS scroll-behavior
The CSS scroll-behavior property accepts one of three values – or two, actually, since one of those was deprecated.
- scroll-behavior: auto is the default instant scrolling behavior that we're already used to.
-
scroll-behavior: instant is the same as
auto
, which is why it was deprecated. If you want it, just useauto
. - scroll-behavior: smooth applies a smooth transition when a scroll event is programmatically triggered.
I say "programmatically triggered" because it's not going to smooth scroll the mouse wheel.
Some ways of programmatically triggering a scroll event are through:
- Window.scrollTo()
- Window.scrollBy()
- Element.scrollTo()
- Element.scrollBy()
- Element.scrollIntoView()
- Element.scrollLeft = x
- Element.scrollTop = y
We'll explore these methods individually.
(Note) Window.scroll()
and Element.scroll()
Maybe you've noticed that I haven't mentioned the scroll() method.
That's because Window.scroll()
and Element.scroll()
are effectively the same methods as Window.scrollTo()
and Element.scrollTo()
. To avoid duplicate content, I'll just refer to scrollTo()
. In practice, you can use either, just choose one and be consistent.
Window.scrollTo() and Element.scrollTo()
This method is ideal for scrolling to absolute coordinates. If you have the x
and y
coordinates for where you want to scroll the user to, you can simply call window.scrollTo(x, y)
and it'll respect the CSS scroll-behavior
of the page
.
The same applies to scrollable elements. You simply call element.scrollTo(x, y)
and it'll respect the CSS scroll-behavior
of the element.
There's also a new signature for this method, which uses an object instead of two numeric arguments, and with this new signature, we can explicitly set our scroll behavior.
// For window
window.scrollTo({
left: x,
top: y,
behavior: 'smooth'
});
// For element
const el = document.querySelector(...);
el.scrollTo({
left: x,
top: y,
behavior: 'smooth'
});
Element.scrollLeft and Element.scrollTop
Setting the element .scrollLeft
and .scrollTop
properties is the same as calling Element.scrollTo()
with the coordinates. It'll respect the CSS scroll-behavior
of the element.
const el = document.querySelector(...);
const x = 100;
const y = 500;
// Setting .scrollLeft and .scrollTop with smooth scroll
el.style.scrollBehavior = 'smooth';
el.scrollLeft = x;
el.scrollTop = y;
// Is the same as calling Element.scrollTo()
el.scrollTo({ left: x, top: y, behavior: 'smooth' });
(Note) Negative Element.scrollLeft
If the direction of your element's text is rtl
, scrollLeft = 0
marks the rightmost position of the horizontal scroll, and the value decreases as you go to the left.
For a scrollable element with 100px
of width, 500px
of scrollable width and direction rtl
, the leftmost position is scrollLeft = -400
.
<div id="scrollable" style="width: 100px; overflow: auto" dir="rtl">
<div style="width: 500px; height: 100px; background: green"></div>
</div>
<p id="output"></p>
const scrollable = document.querySelector('#scrollable');
const output = document.querySelector('#output');
const updateOutput = () => {
output.textContent = `scrollLeft: ${scrollable.scrollLeft}`;
};
updateOutput();
scrollable.addEventListener('scroll', updateOutput);
Window.scrollBy() and Element.scrollBy()
This method has the exact same signatures as Window.scrollTo()
or Element.scrollTo()
. It accepts either x and y as two numeric arguments or a single argument being an object with the optional left
, top
, and behavior
properties.
The difference here is that we're not passing absolute coordinates, but relative values instead. If we scrollBy({ top: 10 })
, we're scrolling 10 pixels down from where we currently are, not 10 pixels down from the beginning of the page.
// For window
window.scrollBy({ top: 10 }); // Scroll 10px down
window.scrollBy({ left: 20 }); // Then 20px to the right
window.scrollBy({ top: 50 }); // And then 50px down
// For element
const el = document.querySelector(...);
el.scrollBy({ top: 10 }); // Scroll 10px down
el.scrollBy({ left: 20 }); // Then 20px to the right
el.scrollBy({ top: 50 }); // And then 50px down
(Note) Window.scrollByLines() and Window.scrollByPages()
Firefox goes a little further by implementing methods to scroll by a number of lines or pages. It's a non-standard feature that only works on Firefox, so you probably don't want to use it in production.
You can achieve a similar effect in all major browsers by converting 100vh
(for a page) and 1rem
(for a line) to pixels and passing that value to Window.scrollBy()
.
const toPixels = require('to-px'); // From NPM
const page = toPixels('100vh');
window.scrollBy(0, page); // window.scrolByPages(1)
const line = toPixels('1rem');
window.scrollBy(0, line); // window.scrolByLines(1)
Element.scrollIntoView()
Most of the time though, we don't care about any hardcoded coordinates, we just want to scroll the user to a specific element on the screen. This can easily (and more explicitly) be done with Element.scrollIntoView()
.
This method can be called with no arguments, that'll scroll the page (respecting the CSS scroll-behavior
) until the element is aligned at the top (unless the element is at the bottom of the page, in that case, it'll scroll as much as possible).
<div style="height: 2000px">Some space</div>
<div id="target" style="background: green">Our element</div>
<div style="height: 500px">More space</div>
const target = document.querySelector('#target');
target.scrollIntoView();
You can further customize the element's placement in the view by passing a boolean or an object.
const el = document.querySelector(...);
// Default, aligns at the top
el.scrollIntoView(true);
// Aligns at the bottom
el.scrollIntoView(false);
// Aligns vertically at the center
el.scrollIntoView({ block: 'center' });
// Vertical top and horizontal center
el.scrollIntoView({ block: 'start', inline: 'center' });
// Vertical bottom and horizontal right
el.scrollIntoView({ block: 'end', inline: 'end' });
// Vertical center and smooth
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
// Vertical nearest
el.scrollIntoView({ block: 'nearest' });
When using an object to define the element's placement, note that block
refers to the vertical placement and inline
refers to the horizontal placement. Also, the 'nearest'
placement can be the top/left or the bottom/right, whichever is nearest, and it can also be nothing if the element is already in view.
Browser Support
{Regarding browser support} As of this writing, all major browsers – except Safari – support smooth scroll and the scrolling methods described in this article.
If you need more reassurance, there's a very good smooth scroll polyfill that I use in my projects. It polyfills scroll()
, scrollTo()
, scrollBy()
, scrollIntoView()
and the CSS scroll-behavior
. It does not support smooth scrolling by setting scrollLeft
/scrollTop
and it does not support the scrollIntoView()
options (it always aligns the element at the top).
If you have more curiosity, I highly suggest studying the polyfill's source code, it's less than 500 lines in total. I made a PR to slightly improve the docs, you might find the code easier to understand with it.
CTA
References for everything I've said and an article with demos for you to play with are in the references below.
Have a great day and see you soon.
References
- CSS
scroll-behavior: smooth
- Mozilla Developer Network - Element.scrollTop- Mozilla Developer Network
- Element.scrollLeft - Mozilla Developer Network
- Smooth scroll polyfill - GitHub iamdustan/smoothscroll
- Smooth scroll polyfill supported methods - I am Dustan
scroll-behavior: instant
renamed toauto
- CSS Working Groupblock
andinline
options forscrollIntoView()
- Stack Overflow- Smooth scroll browser support - Can I Use
- CSS specification for scroll-behavior
- Smooth scroll proposal for scrollTo()
Top comments (0)