video-ad-sdk
Version:
VAST/VPAID SDK that allows video ads to be played on top of any player
100 lines (99 loc) • 4.02 kB
JavaScript
import debounce from 'lodash.debounce';
import { IntersectionObserver } from './helpers/IntersectionObserver';
const validate = (target, callback) => {
if (!(target instanceof Element)) {
throw new TypeError('Target is not an Element node');
}
if (!(callback instanceof Function)) {
throw new TypeError('Callback is not a function');
}
};
const noop = () => { };
const intersectionHandlers = Symbol('intersectionHandlers');
const observerKey = Symbol('intersectionObserver');
const THRESHOLDS_COUNT = 11;
const onIntersection = (target, callback) => {
var _a, _b;
if (!target[intersectionHandlers]) {
target[intersectionHandlers] = [];
const execHandlers = (entries, observer) => {
var _a;
(_a = target[intersectionHandlers]) === null || _a === void 0 ? void 0 : _a.forEach((handler) => handler(entries, observer));
};
const options = {
root: null,
rootMargin: '0px',
threshold: [...new Array(THRESHOLDS_COUNT)].map((_item, index) => index / (THRESHOLDS_COUNT - 1))
};
target[observerKey] = new IntersectionObserver(execHandlers, options);
(_a = target[observerKey]) === null || _a === void 0 ? void 0 : _a.observe(target);
}
(_b = target[intersectionHandlers]) === null || _b === void 0 ? void 0 : _b.push(callback);
return () => {
var _a, _b, _c;
target[intersectionHandlers] = (_a = target[intersectionHandlers]) === null || _a === void 0 ? void 0 : _a.filter((handler) => handler !== callback);
if (((_b = target[intersectionHandlers]) === null || _b === void 0 ? void 0 : _b.length) === 0) {
(_c = target[observerKey]) === null || _c === void 0 ? void 0 : _c.disconnect();
delete target[intersectionHandlers];
delete target[observerKey];
}
};
};
let visibilityHandlers = [];
const onVisibilityChange = (_target, callback) => {
const execHandlers = () => {
if (visibilityHandlers) {
visibilityHandlers.forEach((handler) => handler());
}
};
visibilityHandlers.push(callback);
if (visibilityHandlers.length === 1) {
document.addEventListener('visibilitychange', execHandlers);
}
return () => {
visibilityHandlers = visibilityHandlers.filter((handler) => handler !== callback);
if (visibilityHandlers.length === 0) {
document.removeEventListener('visibilitychange', execHandlers);
}
};
};
let lastIntersectionEntries = [];
/**
* Helper function to know if the visibility of an element has changed.
*
* @ignore
*
* @param target The element that we want to observe.
* @param callback The callback that handles the visibility change.
* @param options Options Map.
*
* @returns Unsubscribe function.
*/
export const onElementVisibilityChange = (target, callback, { threshold = 100, viewabilityOffset = 0.4 } = {}) => {
validate(target, callback);
if (!IntersectionObserver) {
// NOTE: visibility is not determined
callback(undefined);
return noop;
}
let lastIsInViewport = false;
const checkVisibility = (entries) => {
entries.forEach((entry) => {
if (entry.target === target) {
const isInViewport = !document.hidden && entry.intersectionRatio > viewabilityOffset;
if (isInViewport !== lastIsInViewport) {
lastIsInViewport = isInViewport;
callback(isInViewport);
}
}
});
lastIntersectionEntries = entries;
};
const visibilityHandler = debounce(checkVisibility, threshold);
const stopObservingIntersection = onIntersection(target, visibilityHandler);
const stopListeningToVisibilityChange = onVisibilityChange(target, () => visibilityHandler(lastIntersectionEntries));
return () => {
stopObservingIntersection();
stopListeningToVisibilityChange();
};
};