Native Smooth Scroll with pure CSS and JS
Tags: css, javascript, webdev, tutorial
Published: 2022-06-06 17:44:11

Making a smooth scroll with pure CSS and JS

{% embed https://www.youtube.com/embed/En4LgH5Zfjg %}
_See this and many other articles at [lucaspaganini.com](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.

1. **scroll-behavior: auto** is the default instant scrolling behavior that we're already used to.
2. **scroll-behavior: instant** is the same as `auto`, which is why it was deprecated. If you want it, just use `auto`.
3. **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:

```js
-	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.

```js
// 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.

```js
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`.

```html
<div id="scrollable" style="width: 100px; overflow: auto" dir="rtl">
  <div style="width: 500px; height: 100px; background: green"></div>
</div>
<p id="output"></p>
```

<br/>

```js
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.

```js
// 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()`.

```js
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).

```html
<div style="height: 2000px">Some space</div>
<div id="target" style="background: green">Our element</div>
<div style="height: 500px">More space</div>
```

<br/>

```js
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.

```js
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

1. [**CSS** `scroll-behavior: smooth` - Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)
2. [Element.scrollTop- Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop)
3. [Element.scrollLeft - Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft)
4. [Smooth scroll polyfill - GitHub iamdustan/smoothscroll](https://github.com/iamdustan/smoothscroll)
5. [Smooth scroll polyfill supported methods - I am Dustan](http://iamdustan.com/smoothscroll/)
6. [`scroll-behavior: instant` renamed to `auto` - CSS Working Group](https://drafts.csswg.org/cssom-view/#changes-from-2013-12-17)
7. [`block` and `inline` options for `scrollIntoView()` - Stack Overflow](https://stackoverflow.com/a/48635751)
8. [Smooth scroll browser support - Can I Use](https://caniuse.com/css-scroll-behavior)
9. [CSS specification for scroll-behavior](https://drafts.csswg.org/cssom-view/#smooth-scrolling)
10. [Smooth scroll proposal for scrollTo()](https://lists.w3.org/Archives/Public/www-style/2013Mar/0314.html)