• Stars
    star
    1,065
  • Rank 43,376 (Top 0.9 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 8 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

npm stat npm version gzip size size 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
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'
Default: 'center'

Introduced in v2.1.0

More info.

inline

Type: 'start' | 'center' | 'end' | 'nearest'
Default: 'nearest'

Introduced in v2.1.0

More info.

scrollMode

Type: 'always' | 'if-needed'
Default: 'always'

Introduced in v2.1.0

More info.

boundary

Type: Element | Function

Function introduced in v2.1.0, Element introduced in v1.1.0

More info.

skipOverflowHiddenElements

Type: Boolean
Default: false

Introduced in v2.2.0

More info.

TypeScript support

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, { type 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)
}

Breaking API changes from v1

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:

v1

import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'

// Only scrolls into view if needed, and to the nearest edge
scrollIntoViewIfNeeded(target)

v2

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

centerIfNeeded

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.

v1

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)

v2

import scrollIntoView from 'scroll-into-view-if-needed'

scrollIntoView(target, { block: 'center' })
scrollIntoView(target, { block: 'nearest' })

duration

More information.

v1

import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'

scrollIntoViewIfNeeded(target, { duration: 300 })

v2

import scrollIntoView from 'scroll-into-view-if-needed'
// or
import scrollIntoView from 'smooth-scroll-into-view-if-needed'

scrollIntoView(target, { behavior: 'smooth' })

easing

This feature is removed, but you can achieve the same thing by implementing behavior: Function.

handleScroll

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

offset

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.

scrollIntoViewIfNeeded(target, [centerIfNeeded], [animateOptions], [finalElement], [offsetOptions])

This API signature were warned to be dropped in v2.0.0, and it was.

Related packages

Who's using this

  • zeit.co/docs – Documentation of ZEIT Now and other services.
  • Selenium IDE – An integrated development environment for Selenium scripts.
  • Box UI Elements – Box UI Elements are pre-built UI components that allow developers to add elements of the main Box web application into their own applications.
  • react-responsive-ui – Responsive React UI components.
  • Mineral UI – A design system and React component library for the web that lets you quickly build high-quality, accessible apps.
  • Covalent – Teradata UI Platform built on Angular Material.
  • docs.expo.io – Documentation for Expo, its SDK, client and services.
  • Add yourself to the list 😉

Sponsors

Thanks to BrowserStack for sponsoring cross browser and device testing 😄

More Repositories

1

react-spring-bottom-sheet

Accessible ♿️, Delightful ✨, & Fast 🚀
TypeScript
939
star
2

ioredis-mock

Emulates ioredis by performing all operations in-memory.
JavaScript
337
star
3

compute-scroll-into-view

Utility for calculating what should be scrolled, how it's scrolled is up to you
TypeScript
199
star
4

smooth-scroll-into-view-if-needed

Smoothly scroll elements into view, cross browser!
TypeScript
136
star
5

uikit-react

UIkit components built with React
JavaScript
111
star
6

bulma-loader

A Webpack loader for Bulma, a modern CSS framework based on Flexbox
JavaScript
34
star
7

redux-saga-sc

Provides sagas to easily dispatch redux actions over SocketCluster websockets
JavaScript
34
star
8

String.Slugify.js

Extends the String native object to have a slugify method, useful for url slugs.
JavaScript
27
star
9

epic

React example project, that takes you from fun development to high quality production
TypeScript
21
star
10

groqz

(experimental) Transforms GROQ strings to zod schemas in your TypeScript codebase.
TypeScript
14
star
11

nextjs-cv-cms-sanity-v3

My over-engineered CV
TypeScript
11
star
12

String.Inflector.js

Extends the String native with inflector methods, like pluralize and singularize.
9
star
13

uikit-loader

A Webpack CSS loader for UIkit, a lightweight and modular front-end framework for developing fast and powerful web interfaces
JavaScript
9
star
14

Element.Style.Transform.js

Provides a cross browser way of letting you use the CSS3 transform property. Inspired by http://github.com/zachstronaut/jquery-css-transform
JavaScript
9
star
15

public-talks

Slides and other stuff is all in this repo. Each talk is on its own branch
7
star
16

svgdiff

See the visual difference between two SVGs
TypeScript
7
star
17

redux-form-uikit

A set of wrapper components to facilitate using UIkit React with Redux Form
JavaScript
5
star
18

cocody.dev

My own website, wanna do some small blogging and share my fav resources.
JavaScript
3
star
19

react-transform-count-renders

React Transform that lets you console.count how many times your React components render
JavaScript
3
star
20

sanity-meetup-08-22

"v3 does what v2 don't"
TypeScript
3
star
21

system-font-stack

Give your web app a native look by using the font family of the users OS
TypeScript
3
star
22

trains

choo choo 🚂
JavaScript
2
star
23

postcss-import-svg

JavaScript
2
star
24

example-v3-studio

TypeScript
2
star
25

postcss-custom-properties-fallback

Adds fallbacks to your CSS var() functions
JavaScript
2
star
26

next-sanity-preview

JavaScript
2
star
27

express-pretty-error

Express compatible middleware for pretty errors in html, json, raw text, css and terminal contexts with stack traces included!
JavaScript
2
star
28

template-marketing-webapp-nextjs

TypeScript
1
star
29

serve-dynamic-favicon

middleware that serve dynamically generated favicons
JavaScript
1
star
30

example-npm

An example GitHub Action using npm
JavaScript
1
star
31

stipsan.me

My own personal website, don't really need one but it's a good excuse to try new tools and experiment 🚀
JavaScript
1
star
32

cancelpineapple.pizza

HTML
1
star
33

hyperfokus

Hyperfocus your todos until they're done
TypeScript
1
star
34

themer

Create Sanity Studio v3 themes!
TypeScript
1
star
35

graphql-field-resolver-to-typescript

Export ts definitions from your server .graphql files to strictly type your field resolvers
TypeScript
1
star
36

stipsan

1
star
37

scroll-into-view.dev

TypeScript
1
star
38

links

Because browser bookmarks sucks
1
star
39

renovate-presets

JavaScript
1
star
40

potatoes.fyi

HTML
1
star
41

top-github-code-reviewers

Showcase your most active code reviewers
JavaScript
1
star
42

nextjs-blog-cms-sanity-v3-netlify-test

TypeScript
1
star
43

redux-saga-sc-demo

A demo chat app showing redux-saga-sc in action
JavaScript
1
star
44

vc-app

TypeScript
1
star
45

gulp-purge-sourcemaps

Cleans up after gulp-sourcemaps have done a sourcemaps.write() allowing you to combine streams that generate both dev assets with sourcemaps and minified production assets.
JavaScript
1
star
46

sanity-template-gatsby-lps

JavaScript
1
star
47

example-v3-studio-next-runtime

A fork of stipsan/example-v3-studio to get around Vercel's "3 linked projects per repo" limit
TypeScript
1
star