ArticleTemplates/assets/js/modules/services/impl/viewport-scroll.js (36 lines of code) (raw):
let listening = false;
let elementCount = 0;
const elements = Object.create(null);
function observe(element, threshold, callback) {
if (!listening) {
window.addEventListener('scroll', onScroll);
listening = true;
}
elements[threshold] || (elements[threshold] = []);
elements[threshold].push({ element, callback });
elementCount += 1;
}
function unobserve(element, threshold, callback) {
if (!elements[threshold]) return;
const lengthBefore = elements[threshold].length;
elements[threshold] = elements[threshold].filter(record => record.element !== element && record.callback !== callback);
elementCount -= lengthBefore - elements.length;
if (elementCount === 0) {
window.removeEventListener('scroll', onScroll);
listening = false;
}
}
function onScroll() {
const viewportHeight = window.innerHeight;
Object.keys(elements).forEach(threshold => {
const visibleElements = elements[threshold].forEach(record => {
const rect = record.element.getBoundingClientRect();
const isNotHidden =
rect.top + rect.left + rect.right + rect.bottom !== 0;
const area = (rect.bottom - rect.top) * (rect.right - rect.left);
const visibleArea = rect.bottom <= 0
? 0
: rect.top >= viewportHeight
? 0
: (Math.min(viewportHeight, rect.bottom) - Math.max(0, rect.top)) * (rect.right - rect.left);
const intersectionRatio = visibleArea / area;
if (isNotHidden && intersectionRatio >= threshold) {
setTimeout(record.callback, 0, visibleArea);
}
});
});
}
export default { observe, unobserve };