Associates an event emitter with an element to broadcast its position in the viewport with respect to the scroll position of a container.
Borrowing heavily from the scrollMonitor package, ScrollTrack implements the standard Node event emitter in
place of the custom implementation used by ScrollMonitor. ScrollTrack also (subjectively) simplifies the code base, uses transpiled ES6 classes,
has full test coverage, supports vh
-based offsets, and addresses a few (subjective) bugs.
The module consists of a ScrollElementContainer
class and a ScrollElement
class. By default, when required, the module instantiates a singleton
instance of ScrollElementContainer
and exports it. Therefore, when creating a new ScrollElement
(via ScrollElementContainer.create()
), the
ScrollElement
is added to the singleton and available globally (assuming you have a reference to the module on a given module). This approach will
avoid adding additional scroll and resize listeners to the window even if you require the module from multiple places in your project.
To create a new container, do not instantiate a new ScrollElementContainer
. Instead, call the singleton's createContainer
method. This will ensure
that your new container still uses the singleton for capturing scroll
and resize
events (rather than piling on more listeners unneccesarily).
Capturing scroll and resize events on the window can quickly cause performance issues. The module is designed to avoid such issues by implementing the
architecture defined above, while also using passive event listening when available. Passive event listening provides performance-conscientious scroll and
resize event tracking on the window (or any ScrollElementContainer
item).
This module's only dependencies are the events
module, which provides access to the native node EventEmitter
in
a browser environment, and the debounce
module to debounce the resize event listener on the window.
Install via npm:
$ npm i scroll-track
// Exports the ScrollElementContainer singleton
const scrollTrack = require('scroll-track');
const el = document.querySelector('.some-selector');
// NOTE: the create method will throw an error if passed an invalid selector string
const tracker = scrollTrack.create(el);
// or, const tracker = scrollTrack.create('.some-selector');
// or, const tracker = scrollTrack.create(10);
// or, const tracker = scrollTrack.create({top: 10, bottom: 20});
tracker.once('enter-viewport', () => console.log('entered viewport'));
tracker.on('exit-viewport', () => console.log('exited viewport'));
Create watcher objects with scrollMonitor.create(item)
. An optional second argument lets you receive events before or after this element
enters the viewport. See "Offsets".
item
can be one of the following:
Element
- the ScrollElement will watch the area contained by the DOM element.Object
-top
andbottom
properties will be used to set the ScrollElement'stop
andbottom
properties.Number
- the ScrollElement will watch a 1px area this many pixels from the top. Negative numbers will watch from the bottom.String
- it will use the string as a CSS selector and watch the first match.NodeList / Array
- it will use the first DOM element.
ScrollElement are automatically recalculated on the first scroll event after the height of the document changes. They will also fire events immediately after an event listener is attached if appropriate.
ScrollElement.isInViewport
- true if any part of the element is visible, false if not.ScrollElement.isFullyInViewport
- true if the entire element is visible 1.ScrollElement.isAboveViewport
- true if any part of the element is above the viewport.ScrollElement.isBelowViewport
- true if any part of the element is below the viewport.ScrollElement.top
- distance from the top of the document to the top of this watcher.ScrollElement.bottom
- distance from the top of the document to the bottom of this watcher.ScrollElement.height
- top - bottom.ScrollElement.watchItem
- the element, number, or object that this watcher is watching.
ScrollElement.recalculateLocation
- recalculates the location of the element in relation to the document.ScrollElement.getOffsets
- returns an object that defines the top and bottom offsets of this watcher.ScrollElement.destroy
- removes this ScrollElement and its event listeners.ScrollElement.lock
- locks the ScrollElement at its current location.ScrollElement.unlock
- unlocks the ScrollElement.
ScrollElement is an instance of EventEmitter
, so it also has familiar methods such as on
, once
, removeListener
, etc.
Review all inherited methods from the Node events documentation.
These methods are automatically called by the ScrollElementContainer
, but they can be called manually if necessary:
ScrollElement.update
- updates the boolean properties in relation to the viewport. Does not trigger events.ScrollElement.triggerCallbacks
- triggers any callbacks that need to be called.
visibility-change
- when the element enters or exits the viewport.state-change
- similar tovisibility-change
but is also called if the element goes from below the viewport to above it in one scroll event or when the element goes from partially to fully visible or vice versa.enter-viewport
- when the element enters the viewport.fully-enter-viewport
- when the element is completely in the viewport 2.exit-viewport
- when the element completely leaves the viewport.partially-exit-viewport
- when the element goes from being fully in the viewport to only partially 3.
Sometimes you want to change the element you're watching, but want to continue watching the original area. One common use case is setting
position: fixed
on an element when it exits the viewport, then removing positioning when it when it reenters.
const scrollTrack = require('scroll-track');
const tracker = scrollTrack.create(element);
tracker.lock();
tracker.on('exit-viewport', () => {
element.classList.add('fixed');
});
tracker.on('enter-viewport', () => {
element.classList.remove('fixed');
});
Because the watcher was locked, the scroll monitor will not recalculate its location until unlocked.
If you want to trigger an event when the edge of an element is near the edge of the viewport, you can use offsets. The offset
is the second argument to ScrollElementContainer.create()
. You can also create viewport-height-relative offsets by passing a
string suffixed with vh
.
This will trigger events when an element gets within 200px of the viewport:
const scrollTrack = require('scroll-track');
scrollTrack.create(element, 200);
This will trigger when the element is 200px inside the viewport:
const scrollTrack = require('scroll-track');
scrollTrack.create(element, -200);
If you only want it to affect the top and bottom differently:
const scrollTrack = require('scroll-track');
scrollTrack.create(element, {top: 200, bottom: 50});
If you only want it to affect the top and not the bottom:
const scrollTrack = require('scroll-track');
scrollTrack.create(element, {top: 200});
This will trigger when the element when it is 1 viewport height from entering the view:
const scrollTrack = require('scroll-track');
scrollTrack.create(element, {top: '100vh'});
ScrollElementContainer.viewportTop
- distance from the top of the document to the top of the viewport.ScrollElementContainer.viewportBottom
- distance from the top of the document to the bottom of the viewport.ScrollElementContainer.viewportHeight
- height of the viewport.ScrollElementContainer.contentHeight
- height of the document.
ScrollElementContainer.createContainer(containerElement)
- returns a new ScrollElementContainer that can be used just like the scrollTrack module.ScrollElementContainer.create(item, offsets)
- returns a new watcher.item
is a DOM element, NodeList, CSS selector, an object with .top and .bottom properties, or a number.ScrollElementContainer.update()
- update all ScrollElements within the ScrollElementContainer and trigger their events.ScrollElementContainer.recalculateLocations()
- recalculate the location of all unlocked ScrollElements and trigger events if needed.
By default, the container
for any ScrollElement
created via the ScrollElementContainer.create()
method will be the window.
If you want to create a ScrollElement
within a container element, first create a container, and then call that container's create
method to create a ScrollElement
within it.
const containerEl = document.querySelector('.container');
const container = scrollMonitor.createContainer(containerEl);
const scrollEl = document.querySelector('.some-element');
const scrollElement = container.create(scrollEl);
scrollElement.on('enter-viewport', () => console.log('.some-element has entered the viewport'));
scrollElement.on('exit-viewport', () => console.log('.some-element has left the viewport'));
To run the tests:
$ npm test
To generate a coverage report:
$ npm run coverage
To generate documentation:
$ npm run docs
To generate the demos:
$ npm run demo
To generate a bundle for distribution:
$ npm run dist
To run a watch when authoring new examples:
$ npm run watch
Footnotes
-
If the element is larger than the viewport
isFullyInViewport
is true when the element spans the entire viewport. ↩ -
If the element is larger than the viewport
fully-enter-viewport
will be triggered when the element spans the entire viewport. ↩ -
If the element is larger than the viewport
partially-exit-viewport
will be triggered when the element no longer spans the entire viewport. ↩