How to do exposure in the new era

The exposure buried point, as the name suggests, is the buried point reported when the element appears within the viewport.

There are two main points here:

  • within the viewport
  • element is visible

These two contents determine whether an element is exposed or not.

Monitor exposure

historical scheme

In the past, the solution was not so much to monitor exposure as to monitor scrolling. The general idea is:

  1. Dependency collection: mount via DOM feature, use MutationObservable to manage with a set
  2. Scroll listener: listen for scroll events
  3. Calculate the position: getBoundingClientRect() calculates the relative position of the DOM element of each buried point and the viewport. If it is within the viewport, it will be reported
  4. Clear data: the reported points will be deleted in the set, and will not be reported for the next exposure

Of course, it can also be:

  1. Scroll listener: listen for scroll events
  2. Scan the DOM: document.querySelectorAll to get all elements with buried dots
  3. Calculate the position: getBoundingClientRect() calculates the relative position of the DOM element of each buried point and the viewport. If it is within the viewport, it will be reported
  4. Clear buried point (or record data): Delete the mark of the reported point, and it will not be reported for the next exposure; it can also be marked as reported.

But the disadvantage of this scheme is that it will not be triggered without scrolling, so if I change in place without scrolling, in some designs, there may be no way to trigger the listening event. The compensation scheme may be to go back to the design of MutationObservable and make another judgment during initialization.

The main advantage of this solution is that it has excellent compatibility. From MutationObservable to getBoundingClientRect , it has been around for a long time. From the perspective of this complex browser market in China, the use of this solution is very common.

new plan

Now if you don’t need to consider IE compatibility, you can use IntersectionObservable as a new solution for exposure. Considering the definition of “exposure”, this API is really born for “exposure”.

Document: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Compared with the past, the new solution mainly combines the parts of “scroll monitoring” and “calculating position”, and the browser does this for us. The steps roughly become:

  1. Initialization: new IntersectionObservable At the same time, scan whether there are buried points before initialization, and register intersection.observe(element) for these buried point elements. At the same time, you need to prepare a sent buried point set to avoid repeated exposure.
  2. Mutation monitoring: observe and unobserve as the DOM changes, delete the elements in the set at the same time when unregistering, so that you can continue to report next time.
  3. Triggering Intersection: Since both “scroll monitoring” and “calculation position” are handled by IntersectionObservable , we only need to process the logic after exposure. If it is confirmed to trigger the report, it needs to be pressed into the set after the report to avoid the next report.

In MutationObservable , we do not judge visibility and register directly, because we do not use visibility as a condition for element registration. For details, see “Why is the judgment of visibility very complicated” in the question below. Therefore, in IntersectionObservable , we need to judge the visibility, and the details should be read later. I will not repeat them here, but briefly describe the judgment conditions: intersection state change ( isIntersecting ), whether it has been reported ( Set.has ), element visibility ( display and visibility ) to decide whether to report.

question

Most of the articles will use a longer length to tell you “How to use IntersectionObservable to achieve exposure and buried points (demo)”, which is the upper (small) half of this article. But I guess most people just “saw this API, thought it was feasible, and wrote a demo and an article” without actually landing it, so click here.

If you need to actually develop and put into production, there are many points to consider.

MutationObservable articles

MutationObservable optimization

When we use IntersectionObservable , we can’t use the scroll + scan method, we must combine MutationObservable and IntersectionObservable . At this point, we can’t at least monitor all of them. Listening to all changes in one go from the top of the document is undoubtedly the so-called “unnecessary overhead”.

What we should actually be listening for is:

  • addition and deletion of elements
  • Modification of element buried point identification

Therefore, the most important thing is that when the attribute of the DOM changes, in fact, we only need to monitor the elements that have changed the embedded mark: attributeFilter is one of the optimizations.

MutationObservable Corner Cases

If you are not very attribute MutationObservable , then here are a few cases that you need to pay attention to:

  1. MutationObserver is based on the root node when adding or deleting the DOM, so you need to querySelectorAll again to find the registration of child nodes
  2. The front-end framework is in performance, and may perform some optimization processing when the Virtual DOM is converted to DOM, turning the deletion of the DOM into the change of the attribute.
  3. As long as setAttribute is triggered, the listening event will be triggered without comparing the internal value, so if necessary, you may need to bury the package or the business library to handle it yourself

IntersectionObservable articles

The element itself is larger than the viewport

In the calculation of intersection, if the element is larger than the viewport and your threshold is set to > 0, there is no way to trigger the exposure event. Additional optimizations are required. You can refer to the following issue: https://github.com/ w3c/IntersectionObserver/issues/124#issuecomment-476026505

The approximate solution is that in addition to the exposure you need, threshold needs to be supplemented with a zero value, which becomes [0, config.threshold] , so that elements that exceed the viewport can be exposed immediately:

 if ( entry.boundingClientRect.height <= entry.rootBounds.height && entry.boundingClientRect.width <= entry.rootBounds.width && entry.intersectionRatio < config.threshold ) { // 如果元素小于视口,走config.threshold return } else { // 元素大于视口,走零值}

Why can’t I get rootBounds

In the test, we found that rootBounds rootBounds not explained in MDN.

This answer is in the draft: https://www.w3.org/TR/intersection-observer/#dom-intersectionobserverentry-rootbounds

rootBounds, of type DOMRectReadOnly, readonly, nullable

For a same-origin-domain target , this will be the root intersection rectangle. Otherwise, this will be null . Note that if the target is in a different browsing context than the intersection root, this will be in a different coordinate system than boundingClientRect and intersectionRect.

That is, in an iframe, we can’t get rootBounds to be compliant.

Then there will be a problem with the above judgment logic for rootBounds , and additional acquisition methods are required.

The simplest solution in this scenario is: if it is an iframe, then we take the zero value and do not make a judgment that requires rootBounds . And the judgment of iframe, the simple judgment method is window !== top .

In particular, if the specified root object is displayed, then rootBounds is the value of the root object.

The exposure buried point cannot be triggered when the element is fixed

Details of this part are also in the draft: https://www.w3.org/TR/intersection-observer/#dom-intersectionobserver-thresholds

If the IntersectionObserver is an implicit root observer,
it’s treated as if the root were the top-level browsing context’s document, according to the following rule for document.
If the intersection root is a document,
it’s the size of the document’s viewport (note that this processing step can only be reached if the document is fully active).
Otherwise, if the intersection root has an overflow clip,
it’s the element’s content area.
Otherwise,
it’s the result of running the getBoundingClientRect() algorithm on the intersection root.

The key to this paragraph is content area, content area is related to the box model, and we know that if it is fixed, the element will be separated from the text flow. At this time, it is no longer a child element of root, and it is impossible to judge their intersection. .

The solution is to not display the specified root , or specify the root as document , so that the judgment condition of the content area is not satisfied.

Another point to note is that the compatibility requirements of root: document are relatively high, and it is recommended not to display root.

Why Visibility Judgments Are So Complicated

This is a key design idea that this article wants to introduce.

An element is guaranteed by intersection within the viewport range, but it is a very complicated issue whether it is actually visible or not.

Element visibility theoretically represents “what the user can see”, and is equivalent to being in the viewport range, but it can actually be modified by the following methods (there should be more):

  1. display: none
  2. visibility: hidden
  3. opacity: 0
  4. scale: 0
  5. height & width are 0
  6. z-index occlusion
  7. filter
  8. transform
  9. CSS animation special effects

To ensure that the element (DOM) exists, but it is not actually visible, and the judgment needs to use window.getComputedStyle , and then judge each attribute.

And if you want to change the visibility, that is, to change the CSS properties, two methods are commonly used: the change of the class or the change of the style .

If you want to judge according to the real visibility, the relative performance consumption will be very high. First of all, MutationObservable needs to monitor the changes of class and style . The changes in this part are much more than the changes in the buried point mark itself. Of course, the performance overhead of MutationObservable itself is fine. The biggest problem is that after receiving class and style changes, we need to calculate the final properties through window.getComputedStyle and cover all the above possibilities. The overhead of window.getComputedStyle is relatively large.

Therefore, in MutationObservable , we do not monitor, and in IntersectionObservable , for invisible elements, we may even need to consider CSS animation, which is a lot more complicated for our calculation. In simple scenarios, we usually switch CSS visibility. We will use the change of display . If we need to occupy a place, we will use the switch of visibility . Therefore, these two situations are mainly filtered, and the remaining situation is handed over to the business side to avoid .

Of course, in the future, there will be a V2 version of IntersectionObservable , which can ensure the visibility of elements, but even so, even the official recommends that you don’t need it if you don’t need it, because the performance overhead is too expensive compared to the previous version:

Visibility is much more expensive to compute than intersection. For that reason, Intersection Observer v2 is not intended to be used broadly in the way that Intersection Observer v1 is. Intersection Observer v2 is focused on combatting fraud and should be used only when the visibility information is needed and when Intersection Observer v1 functionality is therefore insufficient.

This part is left to the business side to avoid, in order to achieve a balance between performance and data.

So, how to deal with dialog

The above fixed + visibility scenario is actually very common in our pop-up window element design, because if it is controlled by display or DOM, the gradient animation of the pop-up window will not be realized normally, so we choose to use visibility to implement it.

But because of this, we cannot cancel the exposure judgment of visibility (without triggering the reporting of exposure events), because this will cause the pop-up window to be invisible, but the buried point has been reported, resulting in a large number of data errors.

This is the part that “requires the business side” to deal with. The simple solution is to provide a method of manually reporting exposure and buried points, not just using the dots in the DOM. Otherwise, you have to find a way to re-trigger MutationObservable for re-registration. . Because although it is not reported, the IntersectionObservable monitoring in the viewport range is actually triggered, and it will not be triggered again if it is not scrolled, but the fixed scroll cannot be rolled up, and it falls into an infinite loop.

Other notes

  1. In addition to judging according to the DOM, IntersectionObservable also uses display:none as a calculation condition. When the display changes, it will also trigger exposure.
  2. Different from the old scheme, even if scrolling is not triggered, when the element is registered (calling intersection.observe(element) ), the element is already in the viewport range, and the exposure event will also be triggered, which makes our code a lot simpler.

Summarize

As far as the problems mentioned above are concerned, I have read several details that were not mentioned in the article on the implementation of the buried point library, and the acquisition of the data itself is also tricky. After a large number of integration tests and landing It has been continuously summarized from the actual use.

For the SDK provider, it is necessary to comprehensively weigh “ease of use” + “readable” + “performance” + “compatibility” while considering the case, and mark the remaining issues that need the attention of the business side in the document.

This article is reprinted from https://www.codesky.me/archives/intersection-expose-design.wind
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment