We often need to know whether an element has entered the "viewport"(whether the user can see it) in web development.

The green square above is constantly scrolling and the top will prompt for its visibility.

The traditional implementation way is to call the getBoundingClientRect() method of the target element (green square) after listening to the scroll event to get its coordinates relative to the upper left corner of the viewport, and then determine whether it is within the viewport.The disadvantage of this method is that the scroll event occurs at a high frequency and the amount of calculation is very large, which easily causes performance problems.

Fortunately, there is a new IntersectionObserver API that can automatically "observes" if the element is visible, as Chrome 51+ already supports. Since the essence of visible is that the target element creates a crossover area with the viewport, so this API is called a "Intersection Observer".

API

Its usage is very simple.

var io = new IntersectionObserver(callback, option);

In the above code, IntersectionObserver is a constructor provided by the browser natively, accepting two parameters: the callback parameter is a callback function called when the visibility changes, and the option parameter is a configuration object (this parameter is optional).

The constructor's return value is an observer instance. The instance's observe method can specify which DOM node to observe.

// start observing io.observe(document.getElementById('example')); // stop observing io.unobserve(element); // close observer io.disconnect();

In the above code, the parameter of the observe method is a DOM node object. If you want to observe multiple nodes, call this method multiple times.

io.observe(elementA); io.observe(elementB);

Callback Parameters

When the visibility of the target element changes, the observer's callback function will be invoked.

The callback method is usually triggered twice. Once the target element has just entered the viewport (begin visible) and the other time it completely left the viewport (begin invisible).

var io = new IntersectionObserver( entries => { console.log(entries); } );

In the code above, the callback function takes the form of an arrow function.The argument of the callback function is an array,and each member is an IntersectionObserverEntry object.For example, if the visibility of two observable objects simultaneously changes, the entries array will have two members.

IntersectionObserverEntry Object

The IntersectionObserverEntry object provides information about the target element. There are a total of six properties.

{ time: 3893.92, rootBounds: ClientRect { bottom: 920, height: 1024, left: 0, right: 1024, top: 0, width: 920 }, boundingClientRect: ClientRect { // ... }, intersectionRect: ClientRect { // ... }, intersectionRatio: 0.54, target: element }

The meaning of each property is as follows.

time: The time parameter is the time when the visibility changes. It is a high-precision time-stamp in milliseconds.

target: The target parameter is the observed target element, which is a DOM node object.

rootBounds: The rootBounds parameter is the return value of the getBoundingClientRect() method, which represents the information of the rectangular area of the root element. If there is no root element (that is, scrolling directly with respect to the viewport), the value is null.

boundingClientRect: The boundingClientRect parameter represents the rectangular area of the target element.

intersectionRect: The intersectRect parameter represents the intersection of the target element and the viewport (or root element).

intersectionRatio: The intersectionRatio parameter indicates the visible ratio of the target element, that is, the ratio of the intersectionRect to the boundingClientRect. It is equal to 1 when the target element is completely visible, and it is less than or equal to 0 when the element is completely invisible.

In the figure above, the gray border rectangle represents the viewport, and the dark red rectangles represent the four observed target elements,each of which has its own intersectionRatio.

Example: Lazy Load

Sometimes, we hope that some static resources (such as pictures) will only be loaded when the user scrolls down the page and they enter the viewport, which can save bandwidth and improve web page performance.This is called "Lazy Load".

With the IntersectionObserver API, it is easy to implement "Lazy Load".

function query(selector) { return Array.from(document.querySelectorAll(selector)); } var observer = new IntersectionObserver( function(changes) { changes.forEach(function(change) { var container = change.target; var content = container.querySelector('template').content; container.appendChild(content); observer.unobserve(container); }); } ); query('.lazy-loaded').forEach(function (item) { observer.observe(item); });

In the above code, static resources are loaded and inserted into the DOM only when the target element is visible.

Example: Infinite Scroll

It is also very easy to achieve infinite scrolling with the IntersectionObserver API.

var intersectionObserver = new IntersectionObserver( function (entries) { // if it is not visible, it will return if (entries[0].intersectionRatio <= 0) return; loadItems(10); console.log('Loaded new items'); }); // start observing intersectionObserver.observe( document.querySelector('.scrollerFooter') );

We will place a marker element at the bottom of the page to determine if the user has scrolled the page to the bottom. Once the marker element is visible, it means that the user has browsed to the bottom of the page so that the new items are loaded before the marker element.

The Option Object

The second parameter of the IntersectionObserver constructor is a configuration object. It can set the following properties.

The threshold property

The threshold property determines when the callback function is triggered.It is an array where each member is a threshold value. Its default value is [0], meaning that the callback function will be triggered when the intersection ratio reaches zero.

new IntersectionObserver( entries => {/* ... */}, { threshold: [0, 0.25, 0.5, 0.75, 1] } );

You can also customize this array. For example [0, 0.25, 0.5, 0.75, 1] means that the callback function will be triggered when the target element's intersection ratio is 0%, 25%, 50%, 75%, 100%.

The root property and rootMargin property

Sometimes, the target element not only scrolls through the main window, but also scrolls inside the container element(such as an iframe window).

The IntersectionObserver API supports in-container scrolling.The root property specifies the container element where the target element is located. Note that the container element must be the ancestor node of the target element.

var opts = { root: document.querySelector('.container'), rootMargin: "500px 0px" }; var observer = new IntersectionObserver( callback, opts );

The rootMargin property defines the margins of the root element. It is used to enlarge or reduce the rootBounds, which affects the size of the intersectRect.The rootMargin property uses the CSS's definition, such as 10px 20px 30px 40px.

In this way, whether the main window scrolls or the parent container scrolls, the observer will be triggered whenever the visibility of the target element changes.

Notes

The IntersectionObserver API is asynchronous, so the observe method won't be triggered synchronously when you scroll the target element.

IntersectionObserver should be implemented using requestIdleCallback(), which means that the observer is only executed if the thread is idle.This means that this observer has a very low priority and only executes when other tasks are completed and the browser is idle.