Gathering detailed insights and metrics for scroll-into-view-if-needed
Gathering detailed insights and metrics for scroll-into-view-if-needed
Gathering detailed insights and metrics for scroll-into-view-if-needed
Gathering detailed insights and metrics for scroll-into-view-if-needed
compute-scroll-into-view
The engine that powers scroll-into-view-if-needed
scroll-into-view
scrolls an elements into view, recursively aligning parents.
smooth-scroll-into-view-if-needed
Ponyfill for smooth scrolling elements into view (if needed!)
react-scroll-into-view-if-needed
A thin component wrapper around scroll-into-view-if-needed
Element.scrollIntoView ponyfills for things like "if-needed" and "smooth"
npm install scroll-into-view-if-needed
Typescript
Module System
Node Version
NPM Version
JavaScript (77.96%)
HTML (13.96%)
TypeScript (6.54%)
CSS (1.53%)
Total Downloads
487,438,518
Last Day
147,172
Last Week
3,224,499
Last Month
14,046,207
Last Year
142,875,460
MIT License
1,416 Stars
1,106 Commits
76 Forks
11 Watchers
5 Branches
24 Contributors
Updated on Jul 01, 2025
Minified
Minified + Gzipped
Latest Version
3.1.0
Package Id
scroll-into-view-if-needed@3.1.0
Unpacked Size
43.73 kB
Size
10.82 kB
File Count
10
NPM Version
9.6.4
Node Version
18.17.1
Published on
Sep 13, 2023
Cumulative downloads
Total Downloads
Last Day
-4.2%
147,172
Compared to previous day
Last Week
-13.5%
3,224,499
Compared to previous week
Last Month
4.2%
14,046,207
Compared to previous month
Last Year
-5.2%
142,875,460
Compared to previous year
This used to be a ponyfill for
Element.scrollIntoViewIfNeeded
. Since then the CSS working group have decided to implement its features in Element.scrollIntoView
as the option scrollMode: "if-needed"
. Thus this library got rewritten to implement that spec instead of the soon to be deprecated one.
1npm i scroll-into-view-if-needed
You can also use it from a CDN:
1const { default: scrollIntoView } = await import( 2 'https://esm.sh/scroll-into-view-if-needed' 3)
1import scrollIntoView from 'scroll-into-view-if-needed' 2 3const node = document.getElementById('hero') 4 5// similar behavior as Element.scrollIntoView({block: "nearest", inline: "nearest"}) 6// only that it is a no-op if `node` is already visible 7// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView 8// same behavior as Element.scrollIntoViewIfNeeded() 9// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded 10scrollIntoView(node, { 11 scrollMode: 'if-needed', 12 block: 'nearest', 13 inline: 'nearest', 14}) 15 16// same behavior as Element.scrollIntoViewIfNeeded(true) without the "IfNeeded" behavior 17// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded 18scrollIntoView(node, { block: 'center', inline: 'center' }) 19// scrollMode is "always" by default 20 21// smooth scroll if the browser supports it and if the element isn't visible 22scrollIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
What does ponyfilling smooth scrolling mean, and why is it implemented in smooth-scroll-into-view-if-needed
instead?
The answer is bundlesize. If this package adds smooth scrolling to browsers that's missing it then the overall bundlesize increases regardless of wether you use this feature or not.
Put it this way:
1import scrollIntoView from 'scroll-into-view-if-needed'
2// Even if all you do is this
3scrollIntoView(node, { scrollMode: 'if-needed' })
4// You would end up with the same bundlesize as people who need
5// smooth scrolling to work in browsers that don't support it natively
6scrollIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
That's why only native smooth scrolling is supported out of the box. There are two common ways you can smooth scroll browsers that don't support it natively. Below is all three, which one is best for you depends on what is the most important to your use case:: load time, consistency or quality.
In many scenarios smooth scrolling can be used as a progressive enhancement. If the user is on a browser that don't implement smooth scrolling it'll simply scroll instantly and your bundlesize is only as large as it has to be.
1import scrollIntoView from 'scroll-into-view-if-needed' 2 3scrollIntoView(node, { behavior: 'smooth' })
If a consistent smooth scrolling experience is a priority and you really don't want any surprises between different browsers and enviroments. In other words don't want to be affected by how a vendor might implement native smooth scrolling, then smooth-scroll-into-view-if-needed
is your best option. It ensures the same smooth scrolling experience for every browser.
1import smoothScrollIntoView from 'smooth-scroll-into-view-if-needed' 2 3smoothScrollIntoView(node, { behavior: 'smooth' })
If you want to use native smooth scrolling when it's available, and fallback to the smooth scrolling ponyfill:
1import scrollIntoView from 'scroll-into-view-if-needed' 2import smoothScrollIntoView from 'smooth-scroll-into-view-if-needed' 3 4const scrollIntoViewSmoothly = 5 'scrollBehavior' in document.documentElement.style 6 ? scrollIntoView 7 : smoothScrollIntoView 8 9scrollIntoViewSmoothly(node, { behavior: 'smooth' })
New API introduced in
v1.3.0
Type: Object
Type: 'auto' | 'smooth' | Function
Default: 'auto'
Introduced in
v2.1.0
'auto'
The auto option unlocks a few interesting opportunities.
The browser will decide based on user preferences wether it should smooth scroll or not.
On top of that you can control/override scrolling behavior through the scroll-behavior
CSS property.
Some people get motion sick from animations. You can use CSS to turn off smooth scrolling in those cases to avoid making them dizzy:
1html, 2.scroll-container { 3 overflow: scroll; 4} 5 6html, 7.scroll-container { 8 scroll-behavior: smooth; 9} 10@media (prefers-reduced-motion) { 11 html, 12 .scroll-container { 13 scroll-behavior: auto; 14 } 15}
'smooth'
Using behavior: 'smooth'
is the easiest way to smooth scroll an element as it does not require any CSS, just a browser that implements it. More information.
Function
When given a function then this library will only calculate what should be scrolled and leave it up to you to perform the actual scrolling.
The callback is given an array over actions. Each action contain a reference to an element that should be scrolled, with its top and left scrolling coordinates.
What you return is passed through, allowing you to implement a Promise interface if you want to (check smooth-scroll-into-view-if-needed
to see an example of that).
1import scrollIntoView from 'scroll-into-view-if-needed'
2const node = document.getElementById('hero')
3
4scrollIntoView(node, {
5 // Your scroll actions will always be an array, even if there is nothing to scroll
6 behavior: (actions) =>
7 // list is sorted from innermost (closest parent to your target) to outermost (often the document.body or viewport)
8 actions.forEach(({ el, top, left }) => {
9 // implement the scroll anyway you want
10 el.scrollTop = top
11 el.scrollLeft = left
12
13 // If you need the relative scroll coordinates, for things like window.scrollBy style logic or whatever, just do the math
14 const offsetTop = el.scrollTop - top
15 const offsetLeft = el.scrollLeft - left
16 }),
17 // all the other options (scrollMode, block, inline) still work, so you don't need to reimplement them (unless you really really want to)
18})
Check the demo to see an example with popmotion and a spring transition.
If you only need the custom behavior you might be better off by using the compute library directly: https://github.com/scroll-into-view/compute-scroll-into-view
Type: 'start' | 'center' | 'end' | 'nearest'
Default: 'center'
Introduced in
v2.1.0
Type: 'start' | 'center' | 'end' | 'nearest'
Default: 'nearest'
Introduced in
v2.1.0
Type: 'always' | 'if-needed'
Default: 'always'
Introduced in
v2.1.0
Type: Element | Function
Function
introduced inv2.1.0
,Element
introduced inv1.1.0
Type: Boolean
Default: false
Introduced in
v2.2.0
When the library itself is built on TypeScript there's no excuse for not publishing great library definitions!
This goes beyond just checking if you misspelled behavior: 'smoooth'
to the return type of a custom behavior:
1const scrolling = scrollIntoView(document.body, { 2 behavior: actions => { 3 return new Promise( 4 ... 5 ) 6 }, 7}) 8// TypeScript understands that scrolling is a Promise, you can safely await on it 9scrolling.then(() => console.log('done scrolling'))
You can optionally use a generic to ensure that options.behavior
is the expected type.
It can be useful if the custom behavior is implemented in another module:
1const customBehavior = actions => { 2 return new Promise( 3 ... 4 ) 5 } 6 7const scrolling = scrollIntoView<Promise<any>>(document.body, { 8 behavior: customBehavior 9}) 10// throws if customBehavior does not return a promise
The options are available for you if you are wrapping this libary in another abstraction (like a React component):
1import scrollIntoView, { type Options } from 'scroll-into-view-if-needed' 2 3interface CustomOptions extends Options { 4 useBoundary?: boolean 5} 6 7function scrollToTarget(selector, options: Options = {}) { 8 const { useBoundary = false, ...scrollOptions } = options 9 return scrollIntoView(document.querySelector(selector), scrollOptions) 10}
Since v1 ponyfilled Element.scrollIntoViewIfNeeded, while v2 ponyfills Element.scrollIntoView, there are breaking changes from the differences in their APIs.
The biggest difference is that the new behavior follows the spec, so the "if-needed" behavior is not enabled by default:
1import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed' 2 3// Only scrolls into view if needed, and to the nearest edge 4scrollIntoViewIfNeeded(target)
1import scrollIntoView from 'scroll-into-view-if-needed' 2 3// Must provide these options to behave the same way as v1 default 4scrollIntoView(target, { block: 'nearest', scrollMode: 'if-needed' })
The old Element.scrollIntoView
api only had two settings, align to top or bottom. Element.scrollIntoViewIfNeeded
had two more, align to the center or nearest edge.
The Element.scrollIntoView
spec now supports these two modes as block: 'center'
and block: 'nearest'
.
Breaking changes sucks, but on the plus side your code is now more portable and will make this library easier to delete from your codebase on the glorious day browser support is good enough.
1import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
2
3// v1.3.x and later
4scrollIntoViewIfNeeded(target, { centerIfNeeded: true })
5scrollIntoViewIfNeeded(target, { centerIfNeeded: false })
6// v1.2.x and earlier
7scrollIntoViewIfNeeded(target, true)
8scrollIntoViewIfNeeded(target, false)
1import scrollIntoView from 'scroll-into-view-if-needed' 2 3scrollIntoView(target, { block: 'center' }) 4scrollIntoView(target, { block: 'nearest' })
1import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed' 2 3scrollIntoViewIfNeeded(target, { duration: 300 })
1import scrollIntoView from 'scroll-into-view-if-needed' 2// or 3import scrollIntoView from 'smooth-scroll-into-view-if-needed' 4 5scrollIntoView(target, { behavior: 'smooth' })
This feature is removed, but you can achieve the same thing by implementing behavior: Function
.
This is replaced with behavior: Function
with one key difference. Instead of firing once per element that should be scrolled, the new API only fire once and instead give you an array so you can much easier batch and scroll multiple elements at the same time. Or sync scrolling with another element if that's the kind of stuff you're into, I don't judge.
1-import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed' 2+import scrollIntoView from 'scroll-into-view-if-needed' 3 4-scrollIntoViewIfNeeded(node, {handleScroll: (el, {scrollTop, scrollLeft}) => { 5- el.scrollTop = scrollTop 6- el.scrollLeft = scrollLeft 7-}}) 8+scrollIntoView(node, {behavior: actions.forEach(({el, top, left}) => { 9+ el.scrollTop = top 10+ el.scrollLeft = left 11+})})
This was always a buggy feature and warned against using in v1 as it might get dropped. It's much safer to use CSS wrapper elements for this kind of thing.
This API signature were warned to be dropped in v2.0.0
, and it was.
Thanks to BrowserStack for sponsoring cross browser and device testing 😄
No vulnerabilities found.
No security vulnerabilities found.