Skip to content

Commit 3b95ed7

Browse files
authored
fix(readme): API docs, migration guide from v1 and list breaking changes (#214)
1 parent dc5e9cd commit 3b95ed7

File tree

3 files changed

+312
-18
lines changed

3 files changed

+312
-18
lines changed

README.md

Lines changed: 308 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1-
# This is the work-in-progress, the current stable version is [here](https://github.com/stipsan/scroll-into-view-if-needed/tree/v1)
2-
31
[![CircleCI Status](https://img.shields.io/circleci/project/github/stipsan/scroll-into-view-if-needed.svg?style=flat-square)](https://circleci.com/gh/stipsan/scroll-into-view-if-needed)
42
[![npm stat](https://img.shields.io/npm/dm/scroll-into-view-if-needed.svg?style=flat-square)](https://npm-stat.com/charts.html?package=scroll-into-view-if-needed)
53
[![npm version](https://img.shields.io/npm/v/scroll-into-view-if-needed.svg?style=flat-square)](https://www.npmjs.com/package/scroll-into-view-if-needed)
4+
[![gzip size][gzip-badge]][unpkg-dist]
5+
[![size][size-badge]][unpkg-dist]
6+
[![module formats: umd, cjs, and es][module-formats-badge]][unpkg-dist]
67
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release)
7-
![scroll-into-view-if-needed](https://user-images.githubusercontent.com/81981/39338772-34165fb2-49c5-11e8-9d29-38acb6842db8.png)
8+
![scroll-into-view-if-needed](https://user-images.githubusercontent.com/81981/39476436-34a4f3ae-4d5c-11e8-9d1c-7fa2fa6288a0.png)
89

9-
This used to be just a [ponyfill](https://ponyfill.com) for
10-
`Element.scrollIntoViewIfNeeded` but is currently being rewritten to cover
11-
`Element.scrollIntoView(ScrollIntoViewOptions)` including the new `scrollMode: "if-needed"` option. This readme will be updated when it's ready for stable
12-
release.
10+
This used to be a [ponyfill](https://ponyfill.com) for
11+
`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.
1312

1413
## [Demo](https://scroll-into-view-if-needed.netlify.com)
1514

1615
## Install
1716

1817
```bash
19-
yarn add scroll-into-view-if-needed@next
18+
yarn add scroll-into-view-if-needed
2019
```
2120

2221
## Usage
2322

2423
```js
24+
// es6 import
2525
import scrollIntoView from 'scroll-into-view-if-needed'
26+
// or es5
27+
const scrollIntoView = require('scroll-into-view-if-needed')
28+
2629
const node = document.getElementById('hero')
2730

2831
// similar behavior as Element.scrollIntoView({block: "nearest", inline: "nearest"})
@@ -47,22 +50,126 @@ scrollIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
4750

4851
### Ponyfill smooth scrolling
4952

50-
If you're ok with a larger bundlesize and want the smooth scrolling behavior to be ponyfilled you can use the [`smooth-scroll-into-view-if-needed`](https://github.com/stipsan/smooth-scroll-into-view-if-needed) addon.
53+
What does ponyfilling smooth scrolling mean, and why is it implemented in [`smooth-scroll-into-view-if-needed`](https://github.com/stipsan/smooth-scroll-into-view-if-needed) instead?
54+
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.
5155

52-
### Custom scrolling transition
56+
Put it this way:
5357

54-
If the default smooth scrolling ponyfill isn't the duration or easing you want,
55-
you can provide your own scrolling logic by giving `behavior` a callback.
58+
```js
59+
import scrollIntoView from 'scroll-into-view-if-needed'
60+
// Even if all you do is this
61+
scrollIntoIntoView(node, { scrollMode: 'if-needed' })
62+
// You end up with the same bundlesize as people who need
63+
// smooth scrolling to work in browsers that don't support it natively
64+
scrollIntoIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
65+
```
66+
67+
Because of this you need to choose a strategy that matches your priorities: load time, consistency or quality.
68+
69+
##### Load time
70+
71+
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.
72+
73+
```js
74+
import scrollIntoView from 'scroll-into-view-if-needed'
75+
76+
scrollIntoView(node, { behavior: 'smooth' })
77+
```
78+
79+
##### Consistency
80+
81+
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`](https://github.com/stipsan/smooth-scroll-into-view-if-needed) is your best option. It ensures the same smooth scrolling experience for every browser.
82+
83+
```js
84+
import smoothScrollIntoView from 'smooth-scroll-into-view-if-needed'
85+
86+
smoothScrollIntoView(node, { behavior: 'smooth' })
87+
```
88+
89+
##### Quality
90+
91+
If you want to use native smooth scrolling when it's available, and fallback to the smooth scrolling ponyfill:
92+
93+
```js
94+
import scrollIntoView from 'scroll-into-view-if-needed'
95+
import smoothScrollIntoView from 'smooth-scroll-into-view-if-needed'
96+
97+
const scrollIntoViewSmoothly =
98+
'scrollBehavior' in document.documentElement.style
99+
? scrollIntoView
100+
: smoothScrollIntoView
101+
102+
scrollIntoViewSmoothly(node, { behavior: 'smooth' })
103+
```
104+
105+
## API
106+
107+
### scrollIntoView(target, [options])
108+
109+
> New API introduced in `v1.3.0`
110+
111+
### options
112+
113+
Type: `Object`
114+
115+
#### behavior
116+
117+
Type: `'auto' | 'smooth' | 'instant' | Function`<br> Default: `'auto'`
118+
119+
> Introduced in `v2.1.0`
120+
121+
##### `'auto'`
122+
123+
The auto option unlocks a few interesting opportunities.
124+
The browser will decide based on user preferences wether it should smooth scroll or not.
125+
On top of that you can control/override scrolling behavior through the [`scroll-behavior`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) CSS property.
126+
127+
Some people get [motion sick from animations](https://css-tricks.com/smooth-scrolling-accessibility/#article-header-id-5). You can use CSS to turn off smooth scrolling in those cases to avoid making them dizzy:
128+
129+
```css
130+
html,
131+
.scroll-container {
132+
overflow: scroll;
133+
}
134+
135+
html,
136+
.scroll-container {
137+
scroll-behavior: smooth;
138+
}
139+
@media (prefers-reduced-motion) {
140+
html,
141+
.scroll-container {
142+
scroll-behavior: auto;
143+
}
144+
}
145+
```
146+
147+
Quick note, in the CSS property the `auto` keyword equals `behavior: 'instant'`, not `behavior: 'auto'` on `scrollIntoView`. **Yes**, this is confusing.
148+
149+
##### `'smooth'`
150+
151+
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.](#ponyfill-smooth-scrolling)
152+
153+
##### `'instant'`
154+
155+
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_.
156+
157+
##### `Function`
158+
159+
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.
160+
161+
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.
162+
What you return is passed through, allowing you to implement a Promise interface if you want to (check [`smooth-scroll-into-view-if-needed`](https://github.com/stipsan/smooth-scroll-into-view-if-needed) to see an example of that).
56163

57164
```js
58165
import scrollIntoView from 'scroll-into-view-if-needed'
59166
const node = document.getElementById('hero')
60167

61168
scrollIntoView(node, {
62169
// Your scroll actions will always be an array, even if there is nothing to scroll
63-
behavior: scrollActions =>
170+
behavior: actions =>
64171
// list is sorted from innermost (closest parent to your target) to outermost (often the document.body or viewport)
65-
scrollActions.forEach(({ el, top, left }) => {
172+
actions.forEach(({ el, top, left }) => {
66173
// implement the scroll anyway you want
67174
el.scrollTop = top
68175
el.scrollLeft = left
@@ -75,4 +182,190 @@ scrollIntoView(node, {
75182
})
76183
```
77184

78-
## More documentation will be added (hang in there)
185+
Check the demo to see an [example with popmotion and a spring transition](https://scroll-into-view-if-needed.netlify.com/#override-behavior).
186+
187+
#### block
188+
189+
Type: `'start' | 'center' | 'end' | 'nearest'`<br> Default: `'center'`
190+
191+
> Introduced in `v2.1.0`
192+
193+
Control the logical scroll position on the y-axis. The spec states that the `block` direction is related to the [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode), but this is not implemented yet in this library.
194+
This means that `block: 'start'` aligns to the top edge and `block: 'end'` to the bottom.
195+
196+
#### inline
197+
198+
Type: `'start' | 'center' | 'end' | 'nearest'`<br> Default: `'nearest'`
199+
200+
> Introduced in `v2.1.0`
201+
202+
Like `block` this is affected by the [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/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.
203+
204+
#### scrollMode
205+
206+
Type: `'always' | 'if-needed'`<br> Default: `'always'`
207+
208+
> Introduced in `v2.1.0`
209+
210+
This is a proposed addition to the spec that you can track here: https://github.com/w3c/csswg-drafts/pull/1805
211+
212+
This library will be updated to reflect any changes to the spec and will provide a migration path.
213+
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](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
214+
215+
#### boundary
216+
217+
Type: `Element | Function`
218+
219+
> `Function` introduced in `v2.1.0`, `Element` introduced in `v1.1.0`
220+
221+
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.
222+
You can use this option to do things like:
223+
224+
* Prevent the browser window from scrolling.
225+
* Scroll things into view below the fold without scrolling to it.
226+
* Scroll elements into view in a list, without scrolling container elements.
227+
* Prematurely optimizing performance instead of code-splitting your app.
228+
229+
You can also pass a function to do more dynamic checks to override the scroll scoping:
230+
231+
```js
232+
scrollIntoView(target, {
233+
boundary: parent => {
234+
// By default `overflow: hidden` elements are allowed, only `overflow: visible | clip` is skipped as
235+
// this is required by the CSSOM spec
236+
if (getComputedStyle(parent)['overflow'] === 'hidden') {
237+
return false
238+
}
239+
240+
return true
241+
},
242+
})
243+
```
244+
245+
# Breaking API changes from v1
246+
247+
Since v1 ponyfilled Element.scrollIntoViewIfNeeded, while v2 ponyfills Element.scrollIntoView, there are breaking changes from the differences in their APIs.
248+
249+
The biggest difference is that the new behavior follows the spec, so the "if-needed" behavior is **not enabled by default:**
250+
251+
##### v1
252+
253+
```js
254+
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
255+
256+
// Only scrolls into view if needed, and to the nearest edge
257+
scrollIntoViewIfNeeded(target)
258+
```
259+
260+
##### v2
261+
262+
```js
263+
import scrollIntoView from 'scroll-into-view-if-needed'
264+
265+
// Must provide these options to behave the same way as v1 default
266+
scrollIntoView(target, { block: 'nearest', scrollMode: 'if-needed' })
267+
```
268+
269+
#### centerIfNeeded
270+
271+
The old `Element.scrollIntoView` api only had two settings, align to top or bottom. [`Element.scrollIntoViewIfNeeded`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) had two more, align to the center or nearest edge.
272+
The `Element.scrollIntoView` spec now supports these two modes as `block: 'center'` and `block: 'nearest'`.
273+
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.
274+
275+
##### v1
276+
277+
```js
278+
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
279+
280+
// v1.3.x and later
281+
scrollIntoViewIfNeeded(target, { centerIfNeeded: true })
282+
scrollIntoViewIfNeeded(target, { centerIfNeeded: false })
283+
// v1.2.x and earlier
284+
scrollIntoViewIfNeeded(target, true)
285+
scrollIntoViewIfNeeded(target, false)
286+
```
287+
288+
##### v2
289+
290+
```js
291+
import scrollIntoView from 'scroll-into-view-if-needed'
292+
293+
scrollIntoView(target, { block: 'center' })
294+
scrollIntoView(target, { block: 'nearest' })
295+
```
296+
297+
#### duration
298+
299+
[More information.](#ponyfill-smooth-scrolling)
300+
301+
##### v1
302+
303+
```js
304+
import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
305+
306+
scrollIntoViewIfNeeded(target, { duration: 300 })
307+
```
308+
309+
##### v2
310+
311+
```js
312+
import scrollIntoView from 'scroll-into-view-if-needed'
313+
// or
314+
import scrollIntoView from 'smooth-scroll-into-view-if-needed'
315+
316+
scrollIntoView(target, { behavior: 'smooth' })
317+
```
318+
319+
#### easing
320+
321+
This feature is removed, but you can achieve the same thing by implementing [`behavior: Function`](#function).
322+
323+
#### handleScroll
324+
325+
This is replaced with [`behavior: Function`](#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.
326+
327+
```diff
328+
-import scrollIntoViewIfNeeded from 'scroll-into-view-if-needed'
329+
+import scrollIntoView from 'scroll-into-view-if-needed'
330+
331+
-scrollIntoViewIfNeeded(node, {handleScroll: (el, {scrollTop, scrollLeft}) => {
332+
- el.scrollTop = scrollTop
333+
- el.scrollLeft = scrollLeft
334+
-}})
335+
+scrollIntoView(node, {behavior: actions.forEach(({el, top, left}) => {
336+
+ el.scrollTop = top
337+
+ el.scrollLeft = left
338+
+})})
339+
```
340+
341+
#### offset
342+
343+
This was always a buggy feature and warned against using in v1 as it might get dropped.
344+
It's much safer to use CSS wrapper elements for this kind of thing.
345+
346+
### scrollIntoViewIfNeeded(target, [centerIfNeeded], [animateOptions], [finalElement], [offsetOptions])
347+
348+
This API signature were warned to be dropped in `v2.0.0`, and it was.
349+
350+
# Related packages
351+
352+
* [smooth-scroll-into-view-if-needed](http://npmjs.com/package/smooth-scroll-into-view-if-needed) – ponyfills smooth scrolling.
353+
* [react-scroll-into-view-if-needed](https://www.npmjs.com/package/react-scroll-into-view-if-needed) – A thin wrapper to scroll your component into view.
354+
* [Don't be shy, add yours!](https://github.com/stipsan/scroll-into-view-if-needed/edit/master/README.md)
355+
356+
# Who's using this
357+
358+
* [zeit.co/docs](https://github.com/zeit/docs) – Documentation of ZEIT Now and other services.
359+
* [Selenium IDE](https://github.com/SeleniumHQ/selenium-ide) – An integrated development environment for Selenium scripts.
360+
* [Box UI Elements](https://github.com/box/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.
361+
* [react-responsive-ui](https://github.com/catamphetamine/react-responsive-ui) – Responsive React UI components.
362+
* [Mineral UI](https://github.com/mineral-ui/mineral-ui)
363+
A design system and React component library for the web that lets you quickly build high-quality, accessible apps.
364+
* [Covalent](https://github.com/Teradata/covalent) – Teradata UI Platform built on Angular Material.
365+
* [docs.expo.io](https://github.com/expo/expo-docs) – Documentation for Expo, its SDK, client and services.
366+
* [Add yourself to the list 😉](https://github.com/stipsan/scroll-into-view-if-needed/edit/master/README.md)
367+
368+
[gzip-badge]: http://img.badgesize.io/https://unpkg.com/scroll-into-view-if-needed/umd-scroll-into-view-if-needed.min.js?compression=gzip&label=gzip%20size&style=flat-square
369+
[size-badge]: http://img.badgesize.io/https://unpkg.com/scroll-into-view-if-needed/umd-scroll-into-view-if-needed.min.js?label=size&style=flat-square
370+
[unpkg-dist]: https://unpkg.com/scroll-into-view-if-needed/umd/
371+
[module-formats-badge]: https://img.shields.io/badge/module%20formats-umd%2C%20cjs%2C%20es-green.svg?style=flat-square

src/compute.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ const alignNearest = (
190190
return 0
191191
}
192192

193-
export const compute = (
193+
export default (
194194
target: Element,
195195
options: Options = {}
196196
): { el: Element; top: number; left: number }[] => {
@@ -280,6 +280,7 @@ export const compute = (
280280
let blockScroll = 0
281281
let inlineScroll = 0
282282

283+
// @TODO handle borders
283284
// @TODO fix the if else pyramid nightmare
284285

285286
if (block === 'start') {

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { compute, Options as ComputeOptions } from './compute'
1+
import compute, { Options as ComputeOptions } from './compute'
22

33
export interface Options {
44
behavior?: 'auto' | 'smooth' | 'instant' | Function
@@ -22,7 +22,7 @@ export default (target: Element, maybeOptions: Options | boolean = true) => {
2222
supportsScrollBehavior = 'scrollBehavior' in document.documentElement.style
2323
}
2424

25-
// Handle alignToTop for legacy reasons
25+
// Handle alignToTop for legacy reasons, to be compatible with the spec
2626
if (maybeOptions === true || maybeOptions === null) {
2727
options = { block: 'start', inline: 'nearest' }
2828
} else if (maybeOptions === false) {

0 commit comments

Comments
 (0)