This article was originally published at: github.com/bigo-fronte… Welcome to follow and reprint.

background

In the process of rendering the graphic list, the Android phone with a lower configuration experienced memory explosion and failed to recycle, resulting in the client crash. We used Android Studio to analyze and found that the problem was caused by the layer rendering engine of WebView. We found that the memory of the layer rendering Graphics portion continued to increase as we pulled down more and more images, and there was no reclamation. Therefore, in this case, using image lazy loading is no longer the solution. After investigation, we adopted the project of Intersection Observer to implement virtual rendering, that is, rendering content only in the visible area.

Why use the Intersection Observer interface

The Intersection Observer API provides a way to asynchronously detect changes in the Intersection of a target element with an ancestor element or viewport. In other words, previously it was not easy to detect whether elements were visible or intersected, and several methods were possible based on listening for scroll events and using the width and height attributes of elements to calculate the current position of elements in a performance-consuming way. The type of method, in the process of listening to scroll event, frequently called Element. GetBoundingClientRect method, makes the browser repeatedly wide high properties of computational elements. Event listeners, and invoking Element. GetBoundingClientRect are run on the main thread, which could block subsequent js code execution, causing performance problems. Using the Intersection Observer interface saves writing code to calculate location, and because the interface is asynchronous, it allows us to monitor the interface without blocking the JS thread.

Realize the principle of

The Intersection Observer interface achieves the developer’s business purpose by observing the relative position of the root element and the target element and firing a callback function at the point where the target element intersects the root element. The developer can set a number that specifies when the target element enters the root element to trigger the callback.

In the GIF above, the root element is a white window and the target element is a small green square. As you can see, as the screen scrolls, the green square gradually appears in the viewport, triggering the callback function when the green square meets the viewport at 25%, 50%, 75%, and 100%, respectively. Of course, we can treat the ancestor of any target element as the root element, which is important for lazy loading, video playback, and so on.

How to use the Intersection Observer API

1. Create an observer

const callback = (entries) = > {};
const options = () = > {};
const observer = new IntersectionObserver(callback, options);
Copy the code

Callback is an intersection handler that is executed whenever the observed element intersects the specified root element or window. Its parameters as IntersectionObserverEntry object, the object records the intersection of some state information, such as element width is high, the cross ratio and other information. Options, which specifies the context of the element to be observed when the callback is executed. For example, when the root element is specified and the position of the observed element is specified, the intersection processing function rootMargin is triggered, which can be seen in MDN

2. Specify the handlers for the intersection

const callback = (entries) = > {
  entries.forEach(entry= > {
    if (entry.isIntersecting) { // When true, the observed element intersects the specified element
      // Do what you want}})}Copy the code

3. Observe the target element

const ele = document.getElementById('target');
observer.observe(ele);
Copy the code

4. Methods available to Intersection Observer

1.Observe (ele): Start observing a specified target element. An observer can call this method multiple times to observe different elements.2.Unobserve (ele): Used to unobserve an element.3.Disconnect (): Using this method, you can cancel the observation of all elements, that is, the elements previously observed through the Observer method are no longer observed, and the corresponding processing function is not executed.Copy the code

Implement visual area rendering

Implementation approach

We set the viewport as the root element, the head container element as the target element, and set our callback function to fire once when more than 50% of the area of the head container is in the viewport (that is, intersecting the viewport), and to fire once when we leave. Callback function for the function, when the intersection and the target element is visible, will head the url address assigned to the background of the head element (or you can use img) url, when to leave will be the background of the target element url blank, save images to ensure that the image rendering layer is the viewing area of the image, only to achieve the purpose of control the footprint image rendering engine.

The specific implementation

  1. First, create a new observer
let hasInterSection;
try {
  require('intersection-observer');
  hasInterSection = true;
} catch (e) {
  hasInterSection = false;
  console.log(e);
}

const observer = hasInterSection && (new IntersectionObserver((entries, options) = > {
    entries.forEach(isIntersectHandler);
  }, {
    rootMargin: '0px 0px 0px 0px'.threshold: [0.5].trackVisibility: true.delay: 300
  }));
Copy the code

Intersection-observer before creating a new observer, try introducing the intersection-Observer plug-in, which is the polyfill of Intersection Observer to accommodate browsers that don’t currently support the interface. After introduction, in the new observer, we passed in some configuration, where our root element is the viewport element and there is no expansion of the viewport scope (rootMargin: ‘0px 0px 0px 0px’,), if you want to expand or shrink the viewport range, you can change the rootMargin value. Here, when the intersection ratio reaches 0.5, we make it show the head, and when leaving, it also hides the head when it reaches 0.5. As for the understanding of threshold, THERE is a good example of MDN: threshold. As for why we set the ratio of 0.5 in our example, it is mainly due to business needs, because in the scrolling process, when a target element enters the viewport, if it does not reach the ratio of 50%, it is considered that it has not occurred, and its avatar is not assigned. TrackVisibility is set to true because we need to determine whether the element is visible. In the callback, we process entries one by one, because an observer can monitor multiple elements, so entries are an array and need to be processed one by one.

  1. The handler of the callback function
function isIntersectHandler(entry) {
  const target = entry.target;
  const isIntersecting = entry.isIntersecting;
  if (isIntersecting) {
    const src = target.dataset.src;
    const ele = target.getElementsByClassName('user-item-thump-icon') [0];
    if (src && entry.isVisible) {
      const styleStr = `url(${src}), url("${defaultAvatar}") `;
      ele.style.backgroundImage = styleStr;
    } else {
      ele.style.backgroundImage = ' '; }}else {
    const ele = target.getElementsByClassName('user-item-thump-icon') [0];
    ele && (ele.style.backgroundImage = ' '); }}Copy the code

In the handler function of the callback function, we process a single entry. First, we judge whether the elements intersect (normally, all the elements that can trigger this function have already intersected), then we get the avatar URL from the dataset of the target element and judge whether it is visible, so as to assign a value to the background URL so that the avatar can be rendered. When left, the background URL is empty so that it is not rendered.

  1. Start looking at the elements
mounted() {
    observer.observe(this.$refs.thump);
  }
Copy the code

Once you start monitoring, you can customize the element’s background URL.

  1. Stop looking at elements
beforeDestroy() {
  observer.unobserve(this.$refs.thump);
}
Copy the code

A couple of points

  1. While the monitor function is executed asynchronously, the callback function is executed on the main thread. If the logic in the callback function is complex, it may affect the execution of the JS thread. In this case, you are advised to use this methodwindow.requestIdleCallback.
  2. For visible of target element, it is affected by multiple factors, such as opacity,translation and other attributes. For details, please refer to Google.

Welcome everyone to leave a message to discuss, wish smooth work, happy life!

I’m bigO front. See you next time.