Encapsulating DOM Exposure: From Scroll Listener to IntersectionObserver
This article describes how to abstract and improve DOM exposure tracking by first using a scroll listener with getBoundingClientRect and then refactoring to a more robust IntersectionObserver implementation, including code examples, configuration details, and compatibility considerations.
With the increasing demand for exposure tracking, the author extracted previously written exposure logic into a reusable module.
Initial Version
The logic relies on window.scroll events and getBoundingClientRect() to determine if an element is within the viewport.
Key code:
function buryExposure (el, fn) {
let elEnter = false; // dom是否进入可视区域
el.exposure = () => {
const { top } = el.getBoundingClientRect();
if (top > 0 && top < window.screen.availHeight) {
if (!elEnter) {
elEnter = true;
fn(el)
}
} else {
elEnter = false;
};
}
document.addEventListener('scroll', el.exposure);
}Two important points:
Checking the top margin against the viewport to decide exposure.
Using a flag ( elEnter ) to avoid repeated callbacks while the element stays visible.
Rewrite with IntersectionObserver
When a requirement arose to track horizontal scrolling inside a container, the scroll‑based approach proved insufficient, and performance concerns about getBoundingClientRect were raised.
The author turned to window.IntersectionObserver , an asynchronous API that observes the intersection of a target element with an ancestor or the viewport.
Typical usage:
const io = new IntersectionObserver(callback, options)
io.observe(DOM)Configuration options:
const observerOptions = {
root: null, // viewport as root
rootMargin: '0px', // no margin
threshold: [...Array(100).keys()].map(x => x / 100) // trigger on every 0.01 change
}Callback example:
(entries) => {
entries.forEach(item => {
if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) {
// partially visible → treat as exposure
} else if (item.intersectionRatio === 0) {
// not visible → reset
}
});
}The observer reports intersectionRatio values, where 1 means fully visible, values between 0 and 1 indicate partial visibility, and 0 means hidden. The isIntersecting property can also be used.
However, the author found that in some real‑world scenarios (e.g., a horizontally scrolling banner), the observer could report an element as intersecting even when the user cannot actually see it because a parent container clips it.
Final Version
To ensure broader compatibility, a polyfill is loaded when IntersectionObserver is unavailable:
// Use W3C polyfill
require('intersection-observer');The final encapsulated function:
/**
* DOM exposure
* @param {object} options 配置参数
* options.DOMs - array of DOM elements to observe
* options.callback - function to call on exposure
* options.parentDom - optional root element for observation
*/
export default function expose (options = {}) {
if (!options.DOMs || !options.callback) {
console.error('Error: 传入监听DOM或者回调函数');
return;
}
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: [...Array(100).keys()].map(x => x / 100)
};
options.parentDom && (observerOptions.root = options.parentDom);
if (window.IntersectionObserver) {
let elEnter = false; // dom是否进入可视区域
const io = new IntersectionObserver((entries) => {
const fn = () => options.callback({ io });
entries.forEach(item => {
if (!elEnter && item.intersectionRatio > 0 && item.intersectionRatio <= 1) {
fn();
elEnter = true;
} else if (item.intersectionRatio === 0) {
elEnter = false;
}
});
}, observerOptions);
options.DOMs.forEach(DOM => io.observe(DOM));
}
}References
Further reading on getBoundingClientRect and IntersectionObserver can be found at the linked articles in the original post.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.