Security News
Cloudflare Adds Security.txt Setup Wizard
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
scroll-into-view-if-needed
Advanced tools
Ponyfill for upcoming Element.scrollIntoView() APIs like scrollMode: if-needed, behavior: smooth and block: center
The scroll-into-view-if-needed npm package is a smooth scroll polyfill for the 'scrollIntoViewIfNeeded' DOM method, which allows elements to be scrolled into the viewport if they are not already visible. It provides a way to scroll an element into view, aligning it according to the specified options only if it is not already in view.
Basic scrolling into view if needed
This is the simplest use case where you can call the method on an element to ensure it is visible in the viewport.
element.scrollIntoViewIfNeeded();
Custom options for scrolling
This allows you to define custom options for the scrolling behavior, such as the alignment of the scrolled-to element within the viewport.
scrollIntoViewIfNeeded(node, {block: 'nearest', inline: 'nearest'});
Polyfill for unsupported browsers
The package can be used as a polyfill to provide the functionality in browsers that do not support 'scrollIntoViewIfNeeded' natively.
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed';
scrollIntoViewIfNeeded(node);
This package is a polyfill for the smooth scroll behavior in the scrollTo, scrollBy, and scrollIntoView methods. It is similar to scroll-into-view-if-needed but focuses on polyfilling the smooth scroll behavior across all scrolling methods.
Similar to scroll-into-view-if-needed, this package provides a function to scroll an element into view. However, it does not specifically check if the element is already in view and always performs the scroll action.
Zenscroll is a vanilla JavaScript library that enables smooth animated scrolling to elements on the page. While it provides similar smooth scrolling functionality, it does not have the conditional 'if-needed' aspect of scroll-into-view-if-needed.
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.
yarn add scroll-into-view-if-needed
The UMD build is also available on unpkg:
<script src="https://unpkg.com/scroll-into-view-if-needed/umd/scroll-into-view-if-needed.min.js"></script>
You can find the library on window.scrollIntoView
.
// es6 import
import scrollIntoView from 'scroll-into-view-if-needed'
// or es5
const scrollIntoView = require('scroll-into-view-if-needed')
const node = document.getElementById('hero')
// similar behavior as Element.scrollIntoView({block: "nearest", inline: "nearest"})
// only that it is a no-op if `node` is already visible
// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
// same behavior as Element.scrollIntoViewIfNeeded()
// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded
scrollIntoView(node, {
scrollMode: 'if-needed',
block: 'nearest',
inline: 'nearest',
})
// same behavior as Element.scrollIntoViewIfNeeded(true) without the "IfNeeded" behavior
// see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded
scrollIntoView(node, { block: 'center', inline: 'center' })
// scrollMode is "always" by default
// smooth scroll if the browser supports it and if the element isn't visible
scrollIntoView(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:
import scrollIntoView from 'scroll-into-view-if-needed'
// Even if all you do is this
scrollIntoIntoView(node, { scrollMode: 'if-needed' })
// You end up with the same bundlesize as people who need
// smooth scrolling to work in browsers that don't support it natively
scrollIntoIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
Because of this you need to choose a strategy that matches your priorities: 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.
import scrollIntoView from 'scroll-into-view-if-needed'
scrollIntoView(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.
import smoothScrollIntoView from 'smooth-scroll-into-view-if-needed'
smoothScrollIntoView(node, { behavior: 'smooth' })
If you want to use native smooth scrolling when it's available, and fallback to the smooth scrolling ponyfill:
import scrollIntoView from 'scroll-into-view-if-needed'
import smoothScrollIntoView from 'smooth-scroll-into-view-if-needed'
const scrollIntoViewSmoothly =
'scrollBehavior' in document.documentElement.style
? scrollIntoView
: smoothScrollIntoView
scrollIntoViewSmoothly(node, { behavior: 'smooth' })
New API introduced in
v1.3.0
Type: Object
Type: 'auto' | 'smooth' | 'instant' | 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:
html,
.scroll-container {
overflow: scroll;
}
html,
.scroll-container {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion) {
html,
.scroll-container {
scroll-behavior: auto;
}
}
Quick note, in the CSS property the auto
keyword equals behavior: 'instant'
, not behavior: 'auto'
on scrollIntoView
. Yes, this is confusing.
'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.
'instant'
This is useful for scenarios where it's certain that smooth scrolling would make an interaction feel sluggish. Like keyboard navigation and other user experiences where the end user expect things to move instantly.
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).
import scrollIntoView from 'scroll-into-view-if-needed'
const node = document.getElementById('hero')
scrollIntoView(node, {
// Your scroll actions will always be an array, even if there is nothing to scroll
behavior: actions =>
// list is sorted from innermost (closest parent to your target) to outermost (often the document.body or viewport)
actions.forEach(({ el, top, left }) => {
// implement the scroll anyway you want
el.scrollTop = top
el.scrollLeft = left
// If you need the relative scroll coordinates, for things like window.scrollBy style logic or whatever, just do the math
const offsetTop = el.scrollTop - top
const offsetLeft = el.scrollLeft - left
}),
// all the other options (scrollMode, block, inline) still work, so you don't need to reimplement them (unless you really really want to)
})
Check the demo to see an example with popmotion and a spring transition.
Type: 'start' | 'center' | 'end' | 'nearest'
Default: 'center'
Introduced in
v2.1.0
Control the logical scroll position on the y-axis. The spec states that the block
direction is related to the writing-mode, but this is not implemented yet in this library.
This means that block: 'start'
aligns to the top edge and block: 'end'
to the bottom.
Type: 'start' | 'center' | 'end' | 'nearest'
Default: 'nearest'
Introduced in
v2.1.0
Like block
this is affected by the writing-mode. In left-to-right pages inline: 'start'
will align to the left edge. In right-to-left it should be flipped. This will be supported in a future release.
Type: 'always' | 'if-needed'
Default: 'always'
Introduced in
v2.1.0
This is a proposed addition to the spec that you can track here: https://github.com/w3c/csswg-drafts/pull/1805
This library will be updated to reflect any changes to the spec and will provide a migration path.
To be backwards compatible with Element.scrollIntoViewIfNeeded
if something is not 100% visible it will count as "needs scrolling". If you need a different visibility ratio your best option would be to implement an Intersection Observer.
Type: Element | Function
Function
introduced inv2.1.0
,Element
introduced inv1.1.0
By default there is no boundary. All the parent elements of your target is checked until it reaches the viewport (document.documentElement
) when calculating layout and what to scroll.
You can use this option to do things like:
You can also pass a function to do more dynamic checks to override the scroll scoping:
scrollIntoView(target, {
boundary: parent => {
// By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as
// this is required by the CSSOM spec
if (getComputedStyle(parent)['overflow'] === 'hidden') {
return false
}
return true
},
})
Type: Boolean
Default: false
Introduced in
v2.2.0
By default the spec states that overflow: hidden
elements should be scrollable because it has been used to allow programatic scrolling. This behavior can sometimes lead to scrolling issues when you have a node that is a child of an overflow: hidden
node.
This package follows the convention adopted by Firefox of setting a boolean option to not scroll all nodes with overflow: hidden
set.
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:
const scrolling = scrollIntoView(document.body, {
behavior: actions => {
return new Promise(
...
)
},
})
// TypeScript understands that scrolling is a Promise, you can safely await on it
scrolling.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:
const customBehavior = actions => {
return new Promise(
...
)
}
const scrolling = scrollIntoView<Promise<any>>(document.body, {
behavior: customBehavior
})
// 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):
import scrollIntoView, { Options } from 'scroll-into-view-if-needed'
interface CustomOptions extends Options {
useBoundary?: boolean
}
function scrollToTarget(selector, options: Options = {}) {
const { useBoundary = false, ...scrollOptions } = options
return scrollIntoView(document.querySelector(selector), scrollOptions)
}
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:
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
// Only scrolls into view if needed, and to the nearest edge
scrollIntoViewIfNeeded(target)
import scrollIntoView from 'scroll-into-view-if-needed'
// Must provide these options to behave the same way as v1 default
scrollIntoView(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.
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
// v1.3.x and later
scrollIntoViewIfNeeded(target, { centerIfNeeded: true })
scrollIntoViewIfNeeded(target, { centerIfNeeded: false })
// v1.2.x and earlier
scrollIntoViewIfNeeded(target, true)
scrollIntoViewIfNeeded(target, false)
import scrollIntoView from 'scroll-into-view-if-needed'
scrollIntoView(target, { block: 'center' })
scrollIntoView(target, { block: 'nearest' })
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
scrollIntoViewIfNeeded(target, { duration: 300 })
import scrollIntoView from 'scroll-into-view-if-needed'
// or
import scrollIntoView from 'smooth-scroll-into-view-if-needed'
scrollIntoView(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.
-import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
+import scrollIntoView from 'scroll-into-view-if-needed'
-scrollIntoViewIfNeeded(node, {handleScroll: (el, {scrollTop, scrollLeft}) => {
- el.scrollTop = scrollTop
- el.scrollLeft = scrollLeft
-}})
+scrollIntoView(node, {behavior: actions.forEach(({el, top, left}) => {
+ el.scrollTop = top
+ el.scrollLeft = left
+})})
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 😄
FAQs
Ponyfill for upcoming Element.scrollIntoView() APIs like scrollMode: if-needed, behavior: smooth and block: center
The npm package scroll-into-view-if-needed receives a total of 2,681,793 weekly downloads. As such, scroll-into-view-if-needed popularity was classified as popular.
We found that scroll-into-view-if-needed demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
Security News
The Socket Research team breaks down a malicious npm package targeting the legitimate DOMPurify library. It uses obfuscated code to hide that it is exfiltrating browser and crypto wallet data.
Security News
ENISA’s 2024 report highlights the EU’s top cybersecurity threats, including rising DDoS attacks, ransomware, supply chain vulnerabilities, and weaponized AI.