SkillAgentSearch skills...

Scroll Into View If Needed

Element.scrollIntoView ponyfills for things like "if-needed" and "smooth"

Install / Use

npx skills add scroll-into-view/scroll-into-view-if-needed

Installs into whichever agent you are using.

README

npm stat npm version [![gzip size][gzip-badge]][unpkg-dist] [![size][size-badge]][unpkg-dist] semantic-release BrowserStack Status

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.

Demo

Install

npm i scroll-into-view-if-needed

You can also use it from a CDN:

const { default: scrollIntoView } = await import(
  'https://esm.sh/scroll-into-view-if-needed'
)

Usage

import scrollIntoView from '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' })

Ponyfill smooth scrolling

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
scrollIntoView(node, { scrollMode: 'if-needed' })
// You would end up with the same bundlesize as people who need
// smooth scrolling to work in browsers that don't support it natively
scrollIntoView(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.

Load time

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' })

Consistency

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' })

Quality

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' })

API

scrollIntoView(target, [options])

New API introduced in v1.3.0

options

Type: Object

behavior

Type: 'auto' | 'smooth' | Function<br> 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;
  }
}

'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).

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.

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

block

Type: 'start' | 'center' | 'end' | 'nearest'<br> Default: 'center'

Introduced in v2.1.0

More info.

inline

Type: 'start' | 'center' | 'end' | 'nearest'<br> Default: 'nearest'

Introduced in v2.1.0

More info.

scrollMode

Type: 'always' | 'if-needed'<br> Default: 'always'

Introduced in v2.1.0

[More info.](https://github.com/

Related Skills

View on GitHub
GitHub Stars1.4k
CategoryDevelopment
Updated13d ago
Forks77

Languages

JavaScript

Security Score

100/100

Audited on Jul 25, 2026

No findings