“This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Click “like” and see again. Collect == learn

preface

To monitor whether the target element is in the viewable area should be a requirement frequently encountered by toC websites. One of the ways we used to do this was to determine whether the distance of the target element from the viewport head is less than the height of the viewport through JS, and if so, to complete an exposure (i.e. to be seen by the user).

From an implementation level, there really isn’t a problem.

However, from the perspective of performance, frequent acquisition of geometric attribute information of elements will trigger the rearrangement and redrawing of the page. Students unfamiliar with the rearrangement and redrawing can refer to this note. Rearrangement and redrawing can be costly in terms of performance, especially if the page has many elements and a relatively complex layout.

Therefore, since it is a high frequency requirement, ECMA will naturally pay attention to it and will launch corresponding solutions. Yes, it is IntersectionObserver as mentioned in the title.

IntersectionObserverintroduce

Tips: If you are already familiar with this API, you are advised to skip the basic introduction of this part and go directly to the third part: [Problems and Status quo]

  1. define

IntersectionObserver API can automatically “observe” whether elements are visible, which is already supported by Chrome 51+. Because the nature of visible is that a target element intersects with the viewport, the API is called a “intersecting viewer.”

  1. use
const io = new IntersectionObserver(callback, option); IO. Observe (document.getelementById ('example')); IO. Observe (document.getelementById ('example')); // Stop observing IO. Serve (element); // Close the observer io.disconnect();Copy the code
  1. callback
const io = new IntersectionObserver( entries => { console.log(entries); });Copy the code

Entries is an array, each member is a IntersectionObserverEntry object. For example, if the visibility of two observed objects changes at the same time, the Entries array will have two members.

  1. option

Root: The specific ancestor element of the listening object. If no value is passed in or the value is null, the top-level document window is used by default.

RootMargin: Calculates the rectangular offsets added to the root bounding box (en-us) during crossover. All offsets can be expressed by pixel (px) or percentage (%). The default value is “0px 0px 0px 0px”. A simple analogy can be made to the margin attribute of an element.

Thresholds: A list of thresholds in ascending order. Each threshold in the list is the ratio of the cross area to the boundary area of the listening object. [This attribute of the author is not very understand, have not used]

This part mainly refers to THE API introduction of MDN and Ruan Yifeng.

Problems and Current Situation

One requirement of the recent document migration is that when the user scrolls the page, the right menu should locate the directory and increase the active status based on the current scrolling position. I’m sure many blog posts and details pages like tabBar have this feature.

The Nuggets also have this:

It’s easy to think of this as monitoring whether the title is in the visible area, and if you scroll to the directory in the description, add active status to the corresponding directory.

With the basic Api above, we’ll soon be hands-on. Write the code that monitors the title and returns the current visible region title.

function createIntersectionObserver() { const lis = markdown.value.$el.querySelectorAll('a.header-anchor'); observe.value = new IntersectionObserver(observeCallback, { rootMargin: `-80px 0px -80px 0px`, }); for (let i = 0; i < lis.length; i++) { const element = lis[i]; observe.value.observe(element); }} Copy the codeCopy the code

As you can see, I’m monitoring all the titles in the article content area. RootMargin is negative above and below in order to narrow the monitoring area. And we’ll talk about that later.

A callback event is triggered when a monitor element passes through the monitor area, with the following callback logic:

function observeCallback(entries) { if (entries[0].intersectionRatio <= 0) return; const id = entries[0].target.attributes.href.value; toc.value.querySelectorAll('a.active').forEach(ele => { ele.removeAttribute('class'); }); toc.value.querySelector('[href="' + id + '"]')? .setAttribute('class', 'active'); } Duplicate codeCopy the code

In this case, I will process the first returned element (at a certain moment, multiple elements cross the intersecting area. Generally, the sliding speed is relatively fast, but the processing of several relationships is not very large, so it is simply processed here). Only if the intersectionRatio is greater than zero, it indicates that the intersectionRatio is inside the monitor. The logic is simple: take the id of the element and set it to active in the directory area without further elaboration.

In fact, there is a problem here, when I just enter the page, the first title or even the first several headings are in the visible area, and sliding will not trigger the detection of the cross area, so that the directory area can not add active state to them, which seems not very reasonable. Take a look at the ele. me Plus documentation:

Obviously, this is what we should achieve, which is that the first title of the directory should be activated when the first title slides to the top with no selection to the right of the initial state.

Let’s just take a wild guess, does it have only the upper part of its surveillance area? As shown in figure:

So let’s do it this way.

Get the current viewport height, adjust the monitoring range

const height = window.innerHeight > 280 ? window.innerHeight : 280; Observe. value = new IntersectionObserver(observeCallback, {// Minimize the intersecting range and limit the height to 120px rootMargin: `-80px 0px -${height - 80 - 120}px 0px`, }); Copy the codeCopy the code

Limit the height to 120px in the header so that the directory is activated when the monitor moves across the monitor area. Let’s take a look at the effect:

It seems to have had the desired effect.

Don’t forget to disable monitoring when the component is uninstalled (destroyed)!

So the question is, when we change the size of the window, how do we ensure that the size of our monitoring area remains the same? After all, we are a monitor set up with pixel absolutes.

You might think that just listening for the Window resize event and calling back to modify rootMargin would be fine. Yeah, I was thinking the same thing. But there was a problem.

The problem

It is clear that the properties of the instance are read-only. The prototype only has the get method. There is no official method for dynamic modification.

When Google failed, I went to github warehouse to make an issue, hoping to attract the official attention and reply, adhering to the principle of throwing out problems first. Interested students can also go to the “like”, help.

To solve

It’s impossible to pin our hopes on official changes to the API. Those of you who know ECMA’s proposal process know that it’s going to be a very long process, so we need to find practical solutions to this problem now.

We eventually solved the problem by resetting IntersectionObserve instance, which may not be very elegant. The implementation logic looks like this:

  1. Listen for the REsize event and trigger the instantiation of IntersectionObserve
debounceResize.value = debounce(handleResize, 300); window.addEventListener('resize', debounceResize.value); function handleResize() { console.log('resize'); createIntersectionObserver(); } Duplicate codeCopy the code
  1. createIntersectionObserverDetermine if it is instantiated, cancel detection and clear the instantiation
observe.value && (observe.value.disconnect(), (observe.value = null)); Copy the codeCopy the code

Note when uninstalling the destroy component, remove the resize listening event to avoid memory leaks. window.removeEventListener(‘resize’, debounceResize.value);

Write in the last

About the author:

  • I front-end developer, yearning for a free career.
  • I have been working for more than two years and have been laying a solid foundation recently.
  • Problems encountered in daily development will also be summarized into articles, deepen memory, review old knowledge.

If you’re like me, curious about the front end and passionate about technology, you might as well pay attention and learn and grow together.

Related article: Lazy image loading by IntersectionObserver in minutes