Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 2x 26x 26x 26x 2x 26x 26x 26x 26x 26x 2x 2x 2x 26x 26x 26x 26x 26x 26x 26x 26x 26x 26x 26x 26x 26x 26x 2x | /** Internal {@link ScrollToOptions | `ScrollToOptions`}: `left` and `top` properties always have values */
interface _ScrollPosition extends ScrollToOptions {
left: number;
top: number;
}
export interface ScrollPositionElement extends ScrollToOptions {
/**
* A valid CSS selector. Some special characters need to be escaped (https://mathiasbynens.be/notes/css-escapes).
* @example
* Here are some examples:
*
* - `.title`
* - `.content:first-child`
* - `#marker`
* - `#marker\~with\~symbols`
* - `#marker.with.dot`: Selects `class="with dot" id="marker"`, not `id="marker.with.dot"`
*
*/
el: string | Element;
}
/** Scroll parameters */
export type ScrollPosition = ScrollToOptions | ScrollPositionElement;
/** Get current window scroll position */
export const winScrollPos = (): _ScrollPosition => ({
left: window.scrollX,
top: window.scrollY
});
/** Get element position for scrolling in document */
function getElementPosition(
el: Element,
offset: ScrollToOptions
): _ScrollPosition {
const docRect = document.documentElement.getBoundingClientRect();
const elRect = el.getBoundingClientRect();
return {
behavior: offset.behavior,
left: elRect.left - docRect.left - (offset.left || 0),
top: elRect.top - docRect.top - (offset.top || 0)
};
}
/** Scroll to specified position */
export function scrollToPosition(position: ScrollPosition): void {
if ('el' in position) {
const positionEl = position.el;
const el =
typeof positionEl === 'string'
? document.querySelector(positionEl)
: positionEl;
if (!el) return;
position = getElementPosition(el, position);
}
if ('scrollBehavior' in document.documentElement.style) {
window.scrollTo(position);
} else {
window.scrollTo(
Number.isFinite(position.left) ? position.left! : window.scrollX,
Number.isFinite(position.top) ? position.top! : window.scrollY
);
}
}
/** Stored scroll positions */
export const scrollPositions = new Map<string, _ScrollPosition>();
const POSITION_KEY = '__scroll_position_key';
/** Save scroll position */
export function saveScrollPosition(
key: string,
scrollPosition = winScrollPos()
) {
scrollPosition = { ...scrollPosition };
scrollPositions.set(key, scrollPosition);
try {
if (location.href !== key) return;
// preserve the existing history state as it could be overridden by the user
const stateCopy = {
...(history.state || {}),
[POSITION_KEY]: scrollPosition
};
history.replaceState(stateCopy, '');
} catch (error) {}
}
/** Get saved scroll position */
export function getSavedScrollPosition(
key: string,
defaultValue: _ScrollPosition | null = null
): _ScrollPosition | null {
const scroll = scrollPositions.get(key) || history.state[POSITION_KEY];
// Saved scroll position should not be used multiple times, next time should use newly saved position
scrollPositions.delete(key);
return scroll || defaultValue;
}
|