2025-11-02 14:35:35 +03:00

239 lines
7.2 KiB
TypeScript

import { isOnServer, isInBrowser, eventTypes } from './constants.js';
import { WatchItem, Offsets, WatchItemInput, ScrollEvent } from './types.js';
import { Watcher } from './watcher.js';
function getViewportHeight(element: HTMLElement) {
if (isOnServer) {
return 0;
}
if (element === document.body) {
return window.innerHeight || document.documentElement.clientHeight;
} else {
return element.clientHeight;
}
}
function getContentHeight(element: HTMLElement) {
if (isOnServer) {
return 0;
}
if (element === document.body) {
// jQuery approach
// whichever is greatest
return Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.documentElement.clientHeight
);
} else {
return element.scrollHeight;
}
}
function scrollTop(element: HTMLElement) {
if (isOnServer) {
return 0;
}
if (element === document.body) {
return (
window.pageYOffset ||
(document.documentElement && document.documentElement.scrollTop) ||
document.body.scrollTop
);
} else {
return element.scrollTop;
}
}
var browserSupportsPassive = false;
if (isInBrowser) {
try {
var opts = Object.defineProperty({}, 'passive', {
get: function () {
browserSupportsPassive = true;
},
});
window.addEventListener('test', null, opts);
} catch (e) {}
}
const useCapture = browserSupportsPassive ? { capture: false, passive: true } : false;
export class ScrollMonitorContainer {
item: HTMLElement;
watchers: Watcher[];
viewportTop: number;
viewportBottom: number;
documentHeight: number;
viewportHeight: number;
contentHeight: number;
containerWatcher: Watcher | undefined;
update: () => void;
recalculateLocations: () => void;
eventTypes = eventTypes;
constructor(item: HTMLElement, parentWatcher?: ScrollMonitorContainer) {
var self = this;
this.item = item;
this.watchers = [];
this.viewportTop = null;
this.viewportBottom = null;
this.documentHeight = getContentHeight(item);
this.viewportHeight = getViewportHeight(item);
this.DOMListener = function () {
ScrollMonitorContainer.prototype.DOMListener.apply(self, arguments);
};
if (parentWatcher) {
this.containerWatcher = parentWatcher.create(item);
}
var previousDocumentHeight: number;
var calculateViewportI;
function calculateViewport() {
self.viewportTop = scrollTop(item);
self.viewportBottom = self.viewportTop + self.viewportHeight;
self.documentHeight = getContentHeight(item);
if (self.documentHeight !== previousDocumentHeight) {
calculateViewportI = self.watchers.length;
while (calculateViewportI--) {
self.watchers[calculateViewportI].recalculateLocation();
}
previousDocumentHeight = self.documentHeight;
}
}
var updateAndTriggerWatchersI;
function updateAndTriggerWatchers() {
// update all watchers then trigger the events so one can rely on another being up to date.
updateAndTriggerWatchersI = self.watchers.length;
while (updateAndTriggerWatchersI--) {
self.watchers[updateAndTriggerWatchersI].update();
}
updateAndTriggerWatchersI = self.watchers.length;
while (updateAndTriggerWatchersI--) {
self.watchers[updateAndTriggerWatchersI].triggerCallbacks(undefined);
}
}
this.update = function () {
calculateViewport();
updateAndTriggerWatchers();
};
this.recalculateLocations = function () {
this.documentHeight = 0;
this.update();
};
}
listenToDOM() {
if (isInBrowser) {
if (this.item === document.body) {
window.addEventListener('scroll', this.DOMListener, useCapture);
} else {
this.item.addEventListener('scroll', this.DOMListener, useCapture);
}
window.addEventListener('resize', this.DOMListener);
this.destroy = function () {
if (this.item === document.body) {
window.removeEventListener('scroll', this.DOMListener, useCapture);
this.containerWatcher.destroy();
} else {
this.item.removeEventListener('scroll', this.DOMListener, useCapture);
}
window.removeEventListener('resize', this.DOMListener);
};
}
}
destroy() {
// noop, override for your own purposes.
// in listenToDOM, for example.
}
DOMListener(event: ScrollEvent) {
//alert('got scroll');
this.updateState();
this.updateAndTriggerWatchers(event);
}
updateState() {
var viewportTop = scrollTop(this.item);
var viewportHeight = getViewportHeight(this.item);
var contentHeight = getContentHeight(this.item);
var needsRecalcuate =
viewportHeight !== this.viewportHeight || contentHeight !== this.contentHeight;
this.viewportTop = viewportTop;
this.viewportHeight = viewportHeight;
this.viewportBottom = viewportTop + viewportHeight;
this.contentHeight = contentHeight;
if (needsRecalcuate) {
let i = this.watchers.length;
while (i--) {
this.watchers[i].recalculateLocation();
}
}
}
updateAndTriggerWatchers(event: ScrollEvent) {
let i = this.watchers.length;
while (i--) {
this.watchers[i].update();
}
i = this.watchers.length;
while (i--) {
this.watchers[i].triggerCallbacks(event);
}
}
createContainer(input: HTMLElement | NodeList | HTMLElement[] | string) {
let item: HTMLElement;
if (typeof input === 'string') {
item = document.querySelector(input) as HTMLElement;
} else if (Array.isArray(input) || input instanceof NodeList) {
item = input[0] as HTMLElement;
} else {
item = input;
}
var container = new ScrollMonitorContainer(item, this);
this.updateState();
container.listenToDOM();
return container;
}
create(input: WatchItemInput, offsets?: Offsets) {
let item: WatchItem;
if (typeof item === 'string') {
item = document.querySelector(item);
} else if (Array.isArray(input) || input instanceof NodeList) {
item = input[0] as HTMLElement;
} else {
item = input as WatchItem;
}
var watcher = new Watcher(this, item, offsets);
this.watchers.push(watcher);
return watcher;
}
/**
* @deprecated since version 1.1
*/
beget(input: WatchItemInput, offsets?: Offsets) {
return this.create(input, offsets);
}
}