/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/core/dom/layout/viewport-observer.js
130 строк
4 KB
Scott Rippey
✨ [bento-list] Implemented bento-list component (#37360)
23 фев 2022, 21:06
Не верифицирован
23 фев 2022, 21:06
483540b
Код
Авторство
О чём код?
import {isIframed} from '#core/dom'; import {removeItem} from '#core/types/array'; import {getWin} from '#core/window'; /** * Returns an IntersectionObserver tracking the Viewport. * * @param {function(Array<IntersectionObserverEntry>):void} ioCallback * @param {Window} win * @param {import('./types').IOOptions} [opts] * @return {IntersectionObserver} */ export function createViewportObserver(ioCallback, win, opts = {}) { const {needsRootBounds, rootMargin, threshold} = opts; const root = // When in an iFrame, we must specify `root = document` // to ensure rootBounds / rootMargin works correctly isIframed(win) && (needsRootBounds || rootMargin) ? // The Document -> Element type conversion is necessary to satisfy the // `IntersectionObserver` constructor extern that only accepts `Element`. /** @type {?} */ (win.document) : undefined; return new win.IntersectionObserver(ioCallback, { threshold, root, rootMargin, }); } /** @type {WeakMap<Window, IntersectionObserver>} */ const viewportObservers = new WeakMap(); /** @type {WeakMap<Element, Array<function(IntersectionObserverEntry):void>>} */ const viewportCallbacks = new WeakMap(); /** * Lazily creates an IntersectionObserver per Window to track when elements * enter and exit the viewport. Fires viewportCallback when this happens. * * @param {Element} element * @param {function(IntersectionObserverEntry):void} callback * @param {import('./types').IOOptions} [opts] * @return {import('#core/types/function/types').UnlistenCallback} clean up closure to unobserve the element */ export function observeIntersections(element, callback, opts) { const win = getWin(element); if (opts) { // If there are opts, the IntersectionObserver isn't reusable const viewportObserverNoCache = createViewportObserver( (entries) => { callback(entries[entries.length - 1]); }, win, opts ); viewportObserverNoCache.observe(element); return () => { viewportObserverNoCache.unobserve(element); }; } let viewportObserver = viewportObservers.get(win); if (!viewportObserver) { viewportObservers.set( win, (viewportObserver = createViewportObserver(ioCallback, win)) ); } let callbacks = viewportCallbacks.get(element); if (!callbacks) { callbacks = []; viewportCallbacks.set(element, callbacks); } callbacks.push(callback); viewportObserver.observe(element); return () => { unobserveIntersections(element, callback); }; } /** * Unsubscribes a callback from receiving IntersectionObserver updates for an element. * * @param {Element} element * @param {function(IntersectionObserverEntry):void} callback */ function unobserveIntersections(element, callback) { const callbacks = viewportCallbacks.get(element); if (!callbacks) { return; } if (!removeItem(callbacks, callback)) { return; } if (callbacks.length) { return; } // If an element has no more observer callbacks, then unobserve it. const win = getWin(element); const viewportObserver = viewportObservers.get(win); viewportObserver?.unobserve(element); viewportCallbacks.delete(element); } /** * Call the registered callbacks for each element that has crossed the * viewport boundary. * * @param {Array<IntersectionObserverEntry>} entries */ function ioCallback(entries) { const seen = new Set(); for (let i = entries.length - 1; i >= 0; i--) { const entry = entries[i]; const {target} = entry; if (seen.has(target)) { continue; } seen.add(target); const callbacks = viewportCallbacks.get(target); if (!callbacks) { continue; } for (let k = 0; k < callbacks.length; k++) { const callback = callbacks[k]; callback(entry); } } }