Last year, while building the FilePond product page I stumbled upon the scrollIntoView API. It's a handy method to instruct the browser to scroll an element into the viewport.
This article was originally published on my personal blog
The scrollIntoView
API can be instructed to animate the scrolling part by adding the behavior
property on the scrollIntoViewOption
object.
element.scrollIntoView({ behavior: 'smooth' });
I quickly jumped on my JavaScript horse and wrote a tiny script to automatically detect clicks on anchors so the browser would animate the jump towards the anchor target. This jump can be really disorienting, so animating this process would improve the user experience quite a bit.
scrollIntoViewOption currently only works on Firefox and Chrome.
I posted it on Twitter and called it a day.
Then, Hans Spieß points out that this can also be done with CSS, WHAT!?
Turns out there's a scroll-behavior
CSS property that we can set to smooth
, it's literally that literal. It's almost like awesome: yes-please
. We can set the scroll-behavior
property to the container we want to exhibit smooth scroll behavior and we're done.
I created a new demo using only CSS.
Before we go nuts and apply this to all our sites, there are a couple of things we need to keep in mind.
Scroll distance matters
If there is a lot of distance to travel, Firefox will skip content to keep the scroll time-limited, while Chrome has a max velocity and will just take its time to get to the target.
We could use Smart CSS to detect long pages and conditionally apply the smooth scroll style.
Accessibility
People might get motion sickness when watching the animation. To circumvent this you can wrap the CSS property in a prefers-reduced-motion
media query. Unfortunately, Chrome does not support this. Safari supports it, but Safari doesn't support smooth scrolling.
.my-smooth-container {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
.my-smooth-container {
scroll-behavior: auto;
}
}
Conclusion
When deciding on a new functionality we shouldn't reach out to JavaScript immediately. We should do a quick search first to find out if it can be done with CSS as well. The scroll-behavior
property can be a nice UX improvement, do make sure you disable it on very long pages and offer an option to disable it to keep your pages accessible.
Top comments (18)
To those concerned about browser support:
Add these polyfills to your HTML:
and use a custom property on
html
in addition to the normal CSS property:The first polyfill is for polyfilling the JavaScript methods
window.scroll({ behavior: 'smooth')
andElement.scrollIntoView()
, the second one (disclosure: written by me) syncs it up with the CSS. There are also ways to use it with IE, and the awesome stuff described here like respecting(prefers-reduced-motion: reduce)
will still work 👍🏻Full docs: jonaskuske.github.io/smoothscroll-... ⚓
Thanks! Interesting approach to use font-family to pass data to JS. Maybe it’s an idea to also (or instead) allow use of a CSS custom property? The font-family trick feels a bit hacky, it might stand in the way of people using your library.
Yep, it's definitely hacky (but totally works)! 😅
But afaik it's the only reliable way to detect CSS unknown to the browser without having to collect/fetch every single stylesheet on a page (with something like
getElementsByTagName
), parse through all of them with a Regex and repeat that every time some styles change. There's actually a library that helps with that so implementation isn't a problem, but it's quite the added runtime cost for such a small feature, so I decided against it.Custom properties are a good idea
(thanks!) and I'll add them, but the use case there is basically just "I need smooth scroll in Edge but don't care about older browsers" as older browsers without
scrollBehavior
most likely lack support for custom properties as well 😐Edit: And yup, the approach is interesting for sure, but I can't really take "credit" for it: took the idea from a quite popular object-fit polyfill – which is why my PostCSS plugin is just a fork of theirs with a few adjustments :D
Don’t forget about Safari ;-)
Might be a good idea to just not have it on IE11 as the performance impact might not be worth it.
Oh, Safari, you're right!
I think you convinced me, I'll go with
--scroll-behavior
as default and only offer thefont-family
detection as option to support legacy browsers like IE 🤞🏻...and I don't think running
getComputedStyle(el).fontFamily
when an anchor with a local href is clicked is so bad for performance that it justifies dropping IE support, do you? Or do you mean the performance impact of polyfilling smooth scroll in general?Awesome.
I haven’t tested it but the scroll behavior itself might be too much.
getComputedStyle returns a live object so you’ll only have to request it once, but even if you requested it multiple times I don’t think it would be problematic.
I'm using it on the documentation site and it works smoothly, even on IE9 with
requestAnimationFrame
substituted assetTimeout(fn, 0)
😅Also used it just fine on a production site that's very heavy on smooth scrolling (while at the same time running a
position: sticky
polyfill runtime), so I'm not too concerned about performance. :)And didn't know
getComputedStyle
returns a live binding, thanks for the info! Will take this into account next time I update the package 👍🏻So, current strategy:
--scroll-behavior
as default/recommended way of adjusting the behaviorfontFamily
and inline styles for legacy support/convenience--scroll-behavior
if they want?To go with these changes, the PostCSS Plugin shall compile
scroll-behavior: smooth
to:and if
browserslist
includes browsers without support for custom properties, compile to:(while accepting
{ customProperty: boolean, fontFamily: boolean }
config so users can overwrite this behavior)Do you have any feedback on this?
(sorry to bother you but your input was very valuable so far 🙂)
Looks good to me! Love the very structured approach. 👏
Thank you for mentioning the accessibility thing! I am (literally) sick of websites that add smooth scrolling with no way to disable it. Personally I don't see the point because it makes everything feel laggy and swimmy and I want my scrolling to have a 1:1 correspondence with my scroll wheel/gesture/whatever, like what my brain expects.
It's often overlooked, glad to hear it's appreciated.
I hate scroll jacking. In this case, we're only animating the move towards a new part of the page, just as with carefully planned animations, it's mostly about not losing context while transitioning between two locations.
The codepen's behavior inside DEV.to embed is fascinating.
It first scrolls DEV.to down until only one line of the embed is left, then scrolls the embed until that remaining line is just after the infinity.
Oh my goodness, yes! Hadn't noticed. It's so weird :D
Nice! I use this for Smooth Scroll.
Short and sweet, I love it!
i actually learned something from it!
Nice but cross browsing issue
caniuse.com/#search=scroll-behavior
Add these to your HTML:
and add
font-family
everywhere you setscroll-behavior
:The first polyfill generally polyfills
scroll({ behavior: 'smooth')
, the second one (disclosure: written by me) syncs it up with the CSS. 👍🏻If you don't like the
font-family
stuff, you can also run your styles through PostCSS with this – or just use an inline style attribute on html:The workaround is not required there. :)
Full docs: jonaskuske.github.io/smoothscroll-... ⚓
This link is in the article 😊
You can make the choice to only use features that are available everywhere or you follow a progressive enhancement strategy and offer users on more modern browsers a better browsing experience (in this case with little effort).