Members
(static, constant) onDrag
Alias for drag
(static, constant) onSwipe
Alias for swipe
Methods
(static) addListenerForEvents(elements, events, handler, optionsopt)
Adds one listener to multiple events
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
elements |
string
|
HTMLElement
|
NodeList
|
The elements or a selector for elements to add the event listeners to |
|
events |
string
|
Array.<string>
|
The event types to add the event listeners for, like |
|
handler |
function
|
The handler to call when the event is triggered. |
|
options |
object
|
<optional> |
The options to pass to the event listeners |
Example
addListenerForEvents('.foo', 'click mouseenter', (e) => { console.log(e.type) })
(static) css(element, styles, transform)
Sets element styles from passed object of styles. Can also transform dash-case to camelCase for CSS properties
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to set styles on |
styles |
object
|
The object of styles to set |
transform |
boolean
|
Whether to transform dash-case to camelCase for CSS properties |
Example
css(document.getElementById('foo'), { 'background-color': 'red', 'font-size': '16px' }, true) // => sets background-color and font-size
css(document.getElementById('foo'), { backgroundColor: 'red', fontSize: '16px' }) // => sets background-color and font-size
(static) cssTimeToMilliseconds(duration) → {number}
Converts a duration string to milliseconds integer
Parameters:
| Name | Type | Description |
|---|---|---|
duration |
string
|
The duration string to convert, e.g. '1s', '100ms', '0.5s' |
Returns:
- Type:
-
number
The duration in milliseconds
Example
convertToMilliseconds('1s') // 1000
convertToMilliseconds('100ms') // 100
convertToMilliseconds('0.5s') // 500
convertToMilliseconds('0.5') // 0
convertToMilliseconds('foo') // 0
(static) decodeHTML(html) → {string}
Decodes HTML entities in a string using the browser's DOMParser. If the DOMParser is not available, it uses a regular expression to decode the basic entities.
Parameters:
| Name | Type | Description |
|---|---|---|
html |
string
|
The HTML string to decode |
Returns:
- Type:
-
string
The decoded HTML string
Example
decodeHTML('<div>foo</div>') // => '<div>foo</div>'
decodeHTML('<div>foo</div><div>bar</div>') // => '<div>foo</div><div>bar</div>'
(static) delegateEvent(selector, eventType, handler) → {MutationObserver|Object|null}
Delegate DOM events. Uses event bubbling with closest() for events that bubble. Uses MutationObserver for non-bubbling events (focus, blur, mouseenter, mouseleave, etc.) to attach listeners directly to matching elements as they appear in the DOM.
Parameters:
| Name | Type | Description |
|---|---|---|
selector |
string
|
The selector to select the elements to delegate the event to |
eventType |
string
|
The event type to delegate, like |
handler |
function
|
The handler to call when the event is triggered. |
Returns:
- Type:
-
MutationObserver|Object|null
For non-bubbling events the MutationObserver instance (with an added destroy() method that also removes the element listeners), for bubbling events a handle with destroy()/disconnect(), or null when MutationObserver is required but unavailable
Example
delegateEvent('.foo', 'click', (e, target) => {
console.log('Clicked on', target)
})
(static) detachElement(element)
Detaches an element from the DOM and returns it
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to detach |
Example
detachElement(element)
// => element
console.log(element.parentNode) // => null
(static) drag(target, opts) → {object|undefined}
Drag event handler
Pointer events, and one gesture at a time. The pointer is captured on the way in - touch and
pen the browser captures implicitly, the mouse it does not - so a mouse drag keeps reporting
after it has left the element, and whatever the pointer crosses on the way hears nothing of
it. What capture does not survive is the element leaving the document: the moment it is
disconnected the spec hands the capture to the document, and insertBefore on a connected
node disconnects it first, so a list that reorders itself by moving the dragged row loses it
on the first crossing. The moves are heard on the document for as long as the gesture lasts,
which is where they land either way. Everything this needs - pointer events, pointer capture,
touch-action - is in Safari 13 and iOS 13.2; below that the listener attaches and nothing
ever arrives.
Two ways in. Hand it an element and it waits for a pointerdown of its own, for as long
as it is attached. Hand it a pointerdown already in flight and it starts that one gesture
now, taking its listeners away again when the pointer is let go - which is what a caller with
a single delegated listener has, and the only shape that works over a list whose rows come
and go. Attaching per row is a listener per row and a re-attach every time the list grows one;
delegating is one listener whatever the list does.
Started from an event, nothing is written into the element: no drag-enabled, no dragging,
no touch-action. It is an element the caller already owns and has already styled, and
touch-action is decided long before a pointerdown is dispatched, so setting it there would
be a promise this cannot keep - put it on the handle in CSS.
A gesture the platform takes away - a scroll it decided was one, a call arriving - reports
dragcancel and no dragend, and never coasts into inertia. That distinction is the whole
reason to listen for it: a cancelled drag is not a drag the person finished.
within is what makes the percentages worth reading. They are measured against the element
being dragged unless it names another, and a handle is a few pixels of the track it runs
along - so xPercentage off a 24px handle answers a question about the handle, and the caller
wanting "how far along the track is this" has to do the sum itself. Pointed at the track it is
that number directly, held to 0-100 at the ends, which is a slider, a splitter or a
before-and-after comparison in one read. maxVelocity: '0.5%' caps the flick in that unit too.
axis is for a handle on a track. The glide is two-dimensional and over once both velocities
have decayed, and a slider reads one of them: a flick along a horizontal track carries some
velocity across it too, and that one can outlive the one along the track by most of a second -
the handle sat still at the wall, draginertiaend not yet said. Named, the other axis reads
0 in every velocityX/velocityY and carries nothing, so the glide is over when this one is.
preventDefaultTouch owns the touch gesture by putting touch-action: none on the element
for as long as the handler is attached, restored by destroy. Preventing the default on the
events cannot do it: by the time a pointermove arrives the browser has already decided the
gesture is a scroll.
The events are named dragstart, drag and dragend, which are also the names the native
HTML drag and drop API uses. A page listening for the native ones on the same element will
hear these too. Renaming them is a breaking change and has not been made; callback is the
way to take one element's drags without that.
What shaped it. compare-images-slider is
the handle on a track: within, axis, and a flick capped in per cent because a comparison
slider is the same gesture at every width. splitter-elemental in
book-of-elements is that read on either axis, and
rearrange-elemental is the delegated way in - one pointerdown over rows that come and go,
losing the pointer capture on the first insertBefore. Every option above is one of those
three hitting a wall, not a guess at what a caller might want.
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
target |
HTMLElement
|
PointerEvent
|
The element to listen for drag gestures on, or a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opts |
object
|
function
|
The options object or the callback to call when a drag gesture is detected
|
Returns:
- Type:
-
object|undefined
The destroy method to remove the event listeners; nothing when refused - not an element, already attached, or already mid-gesture
Example
drag(document.getElementById('foo'), (e) => {
console.log(e.x) // page coordinates
console.log(e.y)
console.log(e.clientX) // viewport coordinates, what getBoundingClientRect answers in
console.log(e.clientY)
console.log(e.relativeX)
console.log(e.relativeY)
console.log(e.xPercentage)
console.log(e.yPercentage)
console.log(e.velocityX)
console.log(e.velocityY)
console.log(e.prevX)
console.log(e.prevY)
console.log(e.pointerType) // 'mouse', 'touch' or 'pen'
})
// a handle running along a track: 0-100 along the track, not along the handle, gliding along
// it alone after a flick capped at half a per cent of the track per millisecond
drag(handle, { within: track, inertia: true, axis: 'x', maxVelocity: '0.5%', callback: (d) => setPosition(d.xPercentage) })
// one delegated listener over a list, whatever the list does next
list.addEventListener('pointerdown', (e) => {
const handle = e.target.closest('[data-handle]')
if (handle) drag(e, { target: handle, callback: (d) => reorder(d.clientY) })
})
element.addEventListener('drag', (e) => { ... })
element.addEventListener('dragstart', (e) => { ... })
element.addEventListener('dragend', (e) => { ... })
element.addEventListener('dragcancel', (e) => { ... })
element.addEventListener('draginertia', (e) => { ... })
element.addEventListener('draginertiaend', (e) => { ... })
(static) encodeHTML(html) → {string}
Encodes HTML entities in a string using the browser's DOMParser. If the DOMParser is not available, it uses a regular expression to encode the basic entities.
Parameters:
| Name | Type | Description |
|---|---|---|
html |
string
|
The HTML string to encode |
Returns:
- Type:
-
string
The encoded HTML string
Example
encodeHTML('<div>foo</div>') // => '<div>foo</div>'
encodeHTML('<div>foo</div><div>bar</div>') // => '<div>foo</div><div>bar</div>'
(static) getFocusableElements(fromopt) → {Array.<HTMLElement>}
Returns all focusable elements from a given element or the document. Focusable elements are those that can be focused by the user, such as links, buttons, inputs, etc. Always use getVisibleFocusableElements instead of this function to ensure that only visible elements are returned, since only visible elements can be focused by the user.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
from |
HTMLElement
|
Element
|
Document
|
<optional> |
document |
The element to get the focusable elements from |
Returns:
- Type:
-
Array.<HTMLElement>
An array of focusable elements
Example
document.body.innerHTML = `
<div id="foo" tabindex="0">Foo</div>
<div id="bar" tabindex="-1">Bar</div>
<button id="baz">Baz</button>
<div id="qux" contenteditable="true">Qux</div>`
<a href="#quux" id="quux">Quux</a>
getFocusableElements() // => [<div id="foo" tabindex="0">Foo</div>, <button id="baz">Baz</button>, <div id="qux" contenteditable="true">Qux</div>, <a href="#quux" id="quux">Quux</a>]
(static) getHorizontalScrollState(element, thresholdopt) → {object}
Gets if the horizontal scroll has reached the start or end of the element.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
element |
HTMLElement
|
The element to check |
||
threshold |
number
|
<optional> |
0 |
The threshold in pixels to consider the scroll as at the start or end |
Returns:
- Type:
-
object
An object with atStart and atEnd properties indicating if the scroll is at the start or end of the element
Example
const el = document.getElementById('foo')
const scrollState = getHorizontalScrollState(el, 10)
console.log(scrollState.atStart) // => true or false
console.log(scrollState.atEnd) // => true or false
(static) getTableData(selector, headers, rowSelectoropt, cellSelectoropt) → {Array.<object>}
Gets table data from a table element, a simple regular table element, or a table like structure. Useful for scraping data.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
selector |
string
|
The selector to select the table element |
||
headers |
Array.<string>
|
string
|
null
|
The headers to use for the data. If 'auto' is passed, the row containing th or the first row will be used as headers |
||
rowSelector |
string
|
<optional> |
'tr' |
The selector to select the rows |
cellSelector |
string
|
<optional> |
'td' |
The selector to select the cells |
Returns:
- Type:
-
Array.<object>
An array of objects with the properties as keys and the cell values as values
Example
document.body.innerHTML = `
<table id="table">
<thead>
<tr>
<th>Foo</th>
<th>Bar</th>
</tr>
</thead>
<tbody>
<tr>
<td>Foo 1</td>
<td>Bar 1</td>
</tr>
<tr>
<td>Foo 2</td>
<td>Bar 2</td>
</tr>
</tbody>
</table>`
getTableData('#table', ['foo', 'bar'])
// => [
// { foo: 'Foo 1', bar: 'Bar 1' },
// { foo: 'Foo 2', bar: 'Bar 2' }
// ]
(static) getTransitionDuration(element, propertyopt) → {number}
Returns the duration a single property will actually transition with, in milliseconds.
Unlike reading getTransitionDurations() by key, this resolves the all keyword: a
transition: all 1s animates height too, it just isn't listed under that name. An
untransitioned property reads as 0, which is also what transition: none gives you -
so a reduced motion media query switching the transition off reads as "no animation".
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
element |
HTMLElement
|
The element to get the transition duration from |
||
property |
string
|
<optional> |
'all' |
The CSS property to get the duration for |
Returns:
- Type:
-
number
The duration in milliseconds, 0 if the property is not transitioned
Example
getTransitionDuration(element, 'height') // 1000 if transition in CSS is set to 'height 1s'
getTransitionDuration(element, 'height') // 300 if transition in CSS is set to 'all 0.3s'
getTransitionDuration(element, 'height') // 0 if transition in CSS is set to 'opacity 1s'
(static) getTransitionDurations(element) → {object.<string, number>}
Returns a map of transition properties and durations
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to get the transition properties and durations from |
Returns:
- Type:
-
object.<string, number>
A map of transition properties and durations
Example
getTransitionDurations(element) // { height: 1000 } if transition in CSS is set to 'height 1s'
getTransitionDurations(element) // { height: 500, opacity: 1000 } if transition in CSS is set to 'height 0.5s, opacity 1s'
(static) getVerticalScrollState(element, thresholdopt) → {object}
Gets if the vertical scroll has reached the start or end of the element.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
element |
HTMLElement
|
The element to check |
||
threshold |
number
|
<optional> |
0 |
The threshold in pixels to consider the scroll as at the start or end |
Returns:
- Type:
-
object
An object with atStart and atEnd properties indicating if the scroll is at the start or end of the element
Example
const el = document.getElementById('foo')
const scrollState = getVerticalScrollState(el, 10)
console.log(scrollState.atStart) // => true or false
console.log(scrollState.atEnd) // => true or false
(static) getVisibleFocusableElements(fromopt, excludeSelectoropt) → {Array.<HTMLElement>}
Returns all visible focusable elements from a given element or the document.
Empty where there is no layout to consult, since isVisible decides every row.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
from |
HTMLElement
|
Element
|
Document
|
<optional> |
document |
The element to get the focusable elements from |
excludeSelector |
string
|
<optional> |
A selector to exclude elements from the result |
Returns:
- Type:
-
Array.<HTMLElement>
An array of visible focusable elements
Example
document.body.innerHTML = `
<div id="foo" tabindex="0">Foo</div>
<div id="bar" tabindex="-1">Bar</div>
<button id="baz" style="display: none;">Baz</button>
<div id="qux" contenteditable="true">Qux</div>`
getVisibleFocusableElements() // => [<div id="foo" tabindex="0">Foo</div>, <div id="qux" contenteditable="true">Qux</div>]
getVisibleFocusableElements(document.body, '#foo') // => [<div id="qux" contenteditable="true">Qux</div>]
(static) insertBeforeElement(targetElement, newElement)
Inserts an element before another element
Parameters:
| Name | Type | Description |
|---|---|---|
targetElement |
HTMLElement
|
The element to insert before |
newElement |
HTMLElement
|
The element to insert |
Example
const target = document.getElementById('target')
const newElement = document.createElement('div')
newElement.id = 'newElement'
insertBeforeElement(target, newElement)
// <div id="newElement"></div>
// <div id="target"></div>
(static) isEmptyElement(element)
Checks if an element is empty
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
Returns:
boolean
Example
document.body.innerHTML = `
<div id="empty-element"></div>
<div id="non-empty-element1">foo</div>
<div id="non-empty-element2"><br></div>`
isEmptyElement(document.getElementById('empty-element')) // => true
isEmptyElement(document.getElementById('non-empty-element1')) // => false
isEmptyElement(document.getElementById('non-empty-element2')) // => false
(static) isHorizontalScrollVisible(element) → {boolean}
Checks if an element is overflowing horizontally used to check if the scrollbar is visible.
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to check |
Returns:
- Type:
-
boolean
True if the element is overflowing, false otherwise
Example
const el = document.getElementById('foo')
isHorizontalScrollVisible(el) // => true or false
(static) isScrollVisible(element) → {boolean}
Checks if an element is overflowing its container either vertically or horizontally used to check if the scrollbar is visible.
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to check |
Returns:
- Type:
-
boolean
True if the element is overflowing, false otherwise
Example
const el = document.getElementById('foo')
isScrollVisible(el) // => true or false
(static) isVerticalScrollVisible(element) → {boolean}
Checks if an element is overflowing its container used to check if the scrollbar is visible.
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to check |
Returns:
- Type:
-
boolean
True if the element is overflowing, false otherwise
Example
const el = document.getElementById('foo')
isVerticalScrollVisible(el) // => true or false
(static) isVisible(element) → {boolean}
If provided element is visible. Checks if the element is not visibility hidden or display none, has no opacity, and has a width and height.
Answers false for everything where there is no layout to consult - jsdom, and any other DOM
without a renderer behind it - because the fallback path ends on the element having a box, and
nothing in such an environment ever has one. That is honest rather than useful: a DOM that
renders nothing cannot say what is visible. Code that has to run under one wants a narrower
question it can actually answer - whether the element sits inside a [hidden] ancestor, say.
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to check |
Returns:
- Type:
-
boolean
True if the element is visible, false otherwise
Example
isVisible(document.getElementById('foo'))
(static) loadImage(src, callbackopt)
Loads an image form a provided source url and calls a callback when it's loaded
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
src |
string
|
The source url of the image |
|
callback |
function
|
<optional> |
The callback to call when the image is loaded |
Example
loadImage('https://example.com/image.png', () => {
console.log('Image loaded')
})
(static) matchesAll(elements, selector) → {boolean}
Check a list of elements if all of them matches a selector
Parameters:
| Name | Type | Description |
|---|---|---|
elements |
Array.<HTMLElement>
|
NodeList
|
HTMLElement
|
The elements to check |
selector |
string
|
The selector to check |
Returns:
- Type:
-
boolean
True if all of the elements matches the selector, false otherwise
Example
document.body.innerHTML = `
<div id="foo"></div>
<div id="bar"></div>
<div id="baz"></div>`
matchesAll(document.querySelectorAll('div'), 'div') // => true
matchesAll(document.querySelectorAll('div'), '#foo') // => false
(static) matchesAny(elements, selector) → {boolean}
Check a list of elements if any of them matches a selector
Parameters:
| Name | Type | Description |
|---|---|---|
elements |
Array.<HTMLElement>
|
NodeList
|
HTMLElement
|
The elements to check |
selector |
string
|
The selector to check |
Returns:
- Type:
-
boolean
True if any of the elements matches the selector, false otherwise
Example
document.body.innerHTML = `
<div id="foo"></div>
<div id="bar"></div>
<div id="baz"></div>`
matchesAny(document.querySelectorAll('div'), '#foo') // => true
matchesAny(document.querySelectorAll('div'), '#qux') // => false
(static) on(selector, eventTypeOrHandler, handleropt) → {MutationObserver|Object|null}
Run a handler on selected elements and on elements added to the DOM with the same selector as a MutationObserver abstraction,
or use it to delegate events as a delegateEvent alias
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
selector |
string
|
The selector to select the elements to run the handler on |
|
eventTypeOrHandler |
string
|
function
|
The event type to delegate, like |
|
handler |
function
|
<optional> |
The handler to call when the event is triggered. |
Returns:
- Type:
-
MutationObserver|Object|null
The MutationObserver instance (with an added destroy() method), a destroy/disconnect handle for bubbling delegated events, or null when MutationObserver is required but unavailable
Example
on('.foo', (el) => {
console.log('Element', el, 'added to the DOM')
})
on('.foo', 'click', (e, target) => {
console.log('Clicked on', target)
})
(static) parseDOM(html, allChildrenopt) → {Node}
Parses HTML string to a DOM Node
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
html |
string
|
The HTML string to parse |
||
allChildren |
boolean
|
<optional> |
false |
If true, all children of the body will be returned, otherwise only the first child |
Returns:
- Type:
-
Node
The parsed DOM Node
Example
parseDOM('<div>foo</div>') // => <div>foo</div>
parseDOM('<div>foo</div><div>bar</div>', true) // => NodeList(2) [div, div]
parseDOM(document.getElementById('foo')) // => <div id="foo"></div>
parseDOM(document.querySelectorAll('div')) // => NodeList(2) [div, div]
(static) proportionalParentCoverResize(elements, ratioopt, offsetopt)
Resizes an element to cover its parent element while maintaining the aspect ratio
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
elements |
string
|
HTMLElement
|
NodeList
|
The elements or a selector for elements to resize |
||
ratio |
number
|
<optional> |
1 |
The ratio to maintain |
offset |
number
|
<optional> |
0 |
An offset to add to the parent element's width and height |
Example
proportionalParentCoverResize('.foo', 16/9, 10)
(static) query(selector, fromopt) → {Array.<Element>|NodeList}
Queries the DOM for elements and returns them. Substitutes for document.querySelectorAll(selector) and JQuery's $(selector)
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
selector |
string
|
HTMLElement
|
Element
|
Array.<(HTMLElement|Element)>
|
NodeList
|
The selector to select elements |
||
from |
HTMLElement
|
Element
|
<optional> |
document |
The element to query from |
Returns:
- Type:
-
Array.<Element>|NodeList
Example
document.body.innerHTML = `
<div id="foo"></div>
<div id="bar"></div>
<div id="baz"></div>`
query('#foo') // => [<div id="foo"></div>]
query(document.getElementById('foo')) // => [<div id="foo"></div>]
query('div') // => [<div id="foo"></div>, <div id="bar"></div>, <div id="baz"></div>]
(static) querySingle(selector, fromopt) → {HTMLElement|Element}
Queries the DOM for a single element and returns it. Substitutes for document.querySelector(selector) and JQuery's $(selector).first()
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
selector |
string
|
HTMLElement
|
Element
|
Array.<(HTMLElement|Element)>
|
NodeList
|
The selector to select an element |
||
from |
HTMLElement
|
Element
|
<optional> |
document |
The element to query from |
Returns:
- Type:
-
HTMLElement|Element
Example
document.body.innerHTML = `
<div id="foo"></div>
<div id="bar"></div>
<div id="baz"></div>`
querySingle('#foo') // => <div id="foo"></div>
querySingle(document.getElementById('foo')) // => <div id="foo"></div>
querySingle(document.querySelector('#foo')) // => <div id="foo"></div>
(static) readOptions(element, schema) → {object}
Read a set of typed options off an element's attributes.
Accepts the bare, kebab-case and data-* spellings of each option, with data-*
winning when both are present, so a widget can be configured whichever way the
markup author reaches for. Only the options actually present come back - absent
ones are left out rather than defaulted, so the result merges cleanly over
whatever defaults the caller holds.
Booleans follow HTML, not JavaScript: a bare attribute parses as an empty string
and means "on", and only false and 0 mean off. Numbers that do not parse are
dropped rather than coming back as NaN.
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to read the attributes from |
schema |
object.<string, ('boolean'|'number'|'string')>
|
A map of camelCase option names to types |
Returns:
- Type:
-
object
The options present on the element, typed
Example
// <div data-page-step="25" exclusive label="FAQ">
readOptions(element, { pageStep: 'number', exclusive: 'boolean', label: 'string', muted: 'boolean' })
// => { pageStep: 25, exclusive: true, label: 'FAQ' }
(static) remove(selector, fromopt)
Removes all elements matching a selector from the DOM
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
selector |
string
|
HTMLElement
|
Element
|
The selector to select elements to remove |
||
from |
HTMLElement
|
Element
|
<optional> |
document |
The element to remove elements from |
Example
document.body.innerHTML = `
<div id="foo"></div>
<div id="bar"></div>
<div id="baz"></div>`
`
remove('#foo, #bar') // => removes #foo and #bar
(static) removeListenerForEvents(elements, events, handler, optionsopt)
Removes one listener from multiple registered events
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
elements |
string
|
HTMLElement
|
NodeList
|
The elements or a selector for elements to remove the event listeners from |
|
events |
string
|
Array.<string>
|
The event types to remove the event listeners for, like |
|
handler |
function
|
The handler to remove |
|
options |
object
|
<optional> |
The options to pass to the event listeners |
Example
removeListenerForEvents('.foo', 'click mouseenter', (e) => { console.log(e.type) })
(static) scrollSpy(elements, callback, optionsopt) → {object|null}
Reports which section the reader is currently in, and calls back when that changes.
The current section is the last one whose top edge has passed the reading line — offset
pixels below the top of the viewport — which is not the same as the topmost section on
screen: a heading scrolled just out of sight is still the section being read. At the very
bottom of the page the last section wins outright, because a final section shorter than the
screen can never reach the line and would otherwise be unreachable. Above the first section
the callback gets null, leaving what the top of the page means to the caller.
Driven by a rAF-throttled scroll listener rather than an IntersectionObserver: an observer
fires only when visibility changes, so scrolling from one heading to the next inside a screen
that already shows both tells it nothing, and it keeps reporting the section before.
Section positions are measured once and cached, so a scrolled frame costs one layout read —
a document-height check — instead of one per section. The cache rebuilds itself on window
resize, through a ResizeObserver on document.body whenever the body changes size, and on
any scrolled frame where the document's height moved — which is what catches a body pinned to
100% height, whose box never resizes while its content grows. An IntersectionObserver over
the sections rides along as a staleness probe: its entries carry each section's rectangle for
free, so a shift that changed no height anywhere — two sections trading equal heights, a
transform settling — is caught the moment a moved section crosses a viewport edge. A shift
that neither changes a height nor crosses an edge answers stale until update() is called;
each observer degrades alone, so a browser missing one keeps every other trigger.
Follows the window's scroll only, not a scrolling container's.
Parameters:
| Name | Type | Attributes | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
elements |
string
|
Element
|
Array.<Element>
|
NodeList
|
The sections to watch, or a selector for them |
|||||||||||
callback |
function
|
Called with the current section and its index, or with |
|||||||||||
options |
object
|
<optional> |
|
Returns:
- Type:
-
object|null
{ update, destroy }, or null when there is no callback or nothing to watch
Example
const spy = scrollSpy('.prose h2[id]', (section) => {
document.querySelectorAll('.toc a').forEach((a) => a.removeAttribute('aria-current'))
if (section) document.querySelector(`.toc a[href="#${section.id}"]`)?.setAttribute('aria-current', 'location')
}, { offset: 64 })
spy.update() // re-measure after a layout change the observers cannot see
spy.destroy() // stop listening
(static) swipe(element, callback, thresholdopt, timeThresholdopt, mouseopt) → {object|null}
Swipe event handler
Two points make the gesture - where the pointer went down and where it came up - so there is
no pointermove listener running on every frame of every scroll down the page. The axis it
travelled furthest along is the one reported, and a gesture as far down as across is neither.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
element |
HTMLElement
|
The element to listen for swipe gestures on |
||
callback |
object
|
function
|
The callback to call when a swipe gesture is detected or the options object with the callback, threshold, timeThreshold and mouse |
||
threshold |
number
|
<optional> |
150 |
The threshold in pixels to trigger the callback. |
timeThreshold |
number
|
<optional> |
0 |
The threshold in milliseconds to trigger the callback. Defaults to 0, which means the callback will be called regardless of the time it took to swipe. |
mouse |
boolean
|
<optional> |
true |
Whether a mouse drag counts as a swipe. Options object only. Turn it off on anything with selectable text, draggable images or links in it - reading a swipe from a mouse costs the page all three. |
Returns:
- Type:
-
object|null
The destroy method to remove the event listeners, or null without an element
Example
swipe(document.getElementById('foo'), (e) => {
console.log(e.direction)
console.log(e.deltaX)
console.log(e.deltaY)
console.log(e.startX)
console.log(e.startY)
console.log(e.endX)
console.log(e.endY)
console.log(e.threshold)
console.log(e.type)
console.log(e.target)
console.log(e.horizontal)
console.log(e.vertical)
console.log(e.horizontalDirection)
console.log(e.verticalDirection)
console.log(e.timeElapsed)
console.log(e.timeThreshold)
})
swipe(document.getElementById('foo'), { callback: onSwipe, threshold: 40, mouse: false })
element.addEventListener('swipe', (e) => { ... })
element.addEventListener('swipestart', (e) => { ... })
element.addEventListener('swipeend', (e) => { ... })
(static) toggleAttributeValue(element, attribute, on, off)
Toggles an attribute value on an element
Parameters:
| Name | Type | Description |
|---|---|---|
element |
HTMLElement
|
The element to toggle the attribute on |
attribute |
string
|
The attribute to toggle |
on |
string
|
Default: 'true' |
off |
string
|
Default: 'false' |
Example
toggleAttributeValue(element, 'aria-expanded', 'true', 'false')
toggleAttributeValue(element, 'aria-expanded')
(static) whyNotSticky(targetopt) → {Array.<object>}
Reports why a sticky element cannot stick.
position: sticky fails silently: no error, no warning, an element that simply never moves.
The usual cause is an ancestor with overflow: hidden, scroll, auto or overlay, which
becomes the scrollport the element sticks inside — and when that ancestor does not itself
scroll, there is nowhere for the element to go. No DevTools reports it: neither engine's
inactive-CSS pass has a rule for sticky, since the declaration is valid and applied, and their
scroll badges mark containers that really do scroll — where the one that breaks stickiness is
a box that never scrolls at all. This reads the ancestors and names the culprit.
Each finding carries a code, the element it is about, the culprit element to look at,
the problem in one sentence and a fix. The codes are detached (not in the document, so
nothing about it computes), not-sticky, no-inset (every inset
is auto, so there is no threshold to stick at), no-room (the containing block is no larger
than the element), dead-scrollport (an ancestor is the scrollport and never scrolls) and
nested-scrollport (the element sticks inside an ancestor rather than to the viewport, which
may well be what you meant).
An empty array means nothing here can see a reason, never that the element sticks. Three things
it cannot see: contain: paint, contain: layout and content-visibility on an ancestor;
html and body overflow, skipped because a non-visible value there propagates to the viewport
and reporting it would fire on the most ordinary markup there is; and a containing block that is
not the element's parent, since no-room measures the parent.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
target |
string
|
Element
|
Array.<Element>
|
NodeList
|
<optional> |
The element or elements to diagnose, or a selector for them. Left out, every sticky element on the page is diagnosed |
Returns:
- Type:
-
Array.<object>
One finding per problem found, [] when none were
Examples
// In the console: every sticky element on the page, and what is wrong with each
console.table(whyNotSticky())
whyNotSticky('#sidebar')
// => [{ code: 'dead-scrollport', element: aside#sidebar, culprit: div.wrap,
// problem: 'this ancestor is the scrollport (`overflow-y: hidden`) and never scrolls...',
// fix: '`overflow-y: clip` clips the same and creates no scrollport' }]