preface

In actual projects, there are still a lot of lazy image loading requirements, especially c-side applications, in some list pages, such as the list of goods, there are a lot of images to display. One optimization we can make is to delay loading of image elements that do not enter the current viewport, reducing the number of requests to enter the page by one.

Lazy loading of images is implemented by not setting SRC for the img element at first, and by loading the image when it is visible during the slide, triggering the SRC setting.

Here is a brief introduction to the implementation of custom directives in VUE. Native JS and React are the same

Common implementation methods:

  • Listening to theonscrollScroll event
  • With the help ofIntersectionObserver

implementation

Listen for onScroll events

By listening for the OnScroll event, when the scroll event is triggered, determine whether the EL element is in the visible area.

The key is how do you tell if an element is in the viewable area?

We can use the getBoundingClientRect method, as explained in MDN:

Element. GetBoundingClientRect () method returns the Element size and its position relative to the viewport. Contains the left, top, right, bottom, x, y, width, and height attributes of the current element, which are read-only in pixels

The viewport defaults to the top left corner of the screen. Load five images to see the printed top valueThe width and height of each image is 300px. It is not hard to see that the top value is the distance from the element to the top of the screen. Slide up slowly and the top value decreases with the sliding distance. So we can draw the simple conclusion:

  • whenTop < current viewport height, can judge the element into the viewport visible area, obtain the viewport height can be combineddocumentElement.clientHeightbody.clientHeight, the specific implementation depends on the compatibility requirements of the project, here only give a general idea.

The code reference for determining that an element reaches the viewable area is as follows:

/** * determines whether the element is in view */
export const isElementInViewport = el= > {
  if (typeofel.getBoundingClientRect ! = ='function') {
    return true
  }

  const clientHeight = _getClientHeight()
  const rect = el.getBoundingClientRect()
  return rect.top < clientHeight
}

// Get the viewport height
const _getClientHeight = () = > {
  const dClientHeight = document.documentElement.clientHeight
  const bodyClientHeight = document.body.clientHeight
  let clientHeight = 0

  if (bodyClientHeight && dClientHeight) {
    clientHeight = bodyClientHeight < dClientHeight ? bodyClientHeight : dClientHeight
  } else {
    clientHeight = bodyClientHeight > dClientHeight ? bodyClientHeight : dClientHeight
  }

  return clientHeight
}
Copy the code

Listen to scroll processing

// Create a map to cache
window.lazyMap = new Map(a)// Listen for scrolling events and add throttling
window.onscroll = throttle(() = > {
  window.lazyMap.forEach((lazyImg, key) = > {
    if (isElementInViewport(lazyImg.el)) {
      lazyImg.el.src = lazyImg.value.src
      lazyImg.value.callback(lazyImg.el)
      window.lazyMap.delete(key)
    }
  })
}, 200)
Copy the code

Secondly, IntersectionObserver is employed

In the past, it was not easy to detect whether an element was visible or whether two elements intersected, and many of the solutions were unreliable or poor performance, such as the first method, which required listening for page scrolling, which affected performance somewhat

The Intersection Observer API now provides a way to asynchronously detect changes in the Intersection of a target element with an ancestor element or viewport.

Where intersection detection can come into play:

  • Image lazy loading – images are loaded only when they are scrolled into view
  • Content infinite scrolling – that is, when the user scrolls near the bottom of the content, more is loaded directly, without the user having to turn the page, giving the user the illusion that the web page can scroll indefinitely
  • Detect AD exposure – In order to calculate AD revenue, you need to know the exposure of AD elements
  • Perform a task or play an animation when the user sees an area

More instructions on IntersectionObserver and how to use the portal

A key attribute used here is isIntersecting, which is a Boolean value indicating whether the target element is converted into an intersection state (true) or out of the intersection state (false). When intersecting, the value of this property is true. Compared to the previous whether to enter the visual area of the judgment, simple is not a little bit 🌹

The core code is

window.observer = new IntersectionObserver(entries= > {
    entries.forEach(entry= > {
      let lazyImage = entry.target
      // Determine whether to intersect
      if (entry.isIntersecting) {
        const src = lazyImage.getAttribute('data-src')
        lazyImage.src = src
        lazyImage.style.opacity = 1
        lazyImage.style.display = 'block'
        // Remove the listener
        window.observer.unobserve(lazyImage)
      }
    })
  })
Copy the code

Custom instruction

Well, back to custom directives, if you are not familiar with the use of custom directives please refer to the official Vue documentation portal

Use these hook functions:

  • Bind: Called only once, the first time a directive is bound to an element. This is where you can perform one-time initialization Settings.
    • Such as listening to theonerrorEvent, set default SRC if loading fails.
    • Set up theonscrollListening to the
    • IntersectionObserverThe initialization
  • Inserted: Called when the bound element is inserted into a parent (the parent is guaranteed to exist, but not necessarily inserted into the document).
    • onscrollIf el isInViewport, set SRC, otherwiseelbindingAdded to thelazyMap
    • IntersectionObserverThe scheme just needs to be calledobserve Listen on target object
  • ComponentUpdated: Invoked when the VNode of the component where the directive resides and its child VNodes are all updated.
    • Same inserted, just need to do a SRC judgment, ifvalueoldValueConsistent, do not proceed
  • Unbind: Called only once, when an instruction is unbound from an element.
    • onscrollScheme that takes the current target element fromlazyMapremove
    • IntersectionObserverScheme, callunobserveRemove target object listening

The final result

conclusion

The existence must have its rationality, so far, the two schemes have their advantages and disadvantages

plan advantages disadvantages
Based on the onscroll Good compatibility Poor performance, need to continuously listen for rolling events; The implementation is not very elegant and the code is heavy
IntersectionObserver Good performance, elegant implementation, less code Poor compatibility, IE does not support it at all, and Safari requires more than 12 support

MDN has a good article on this, if you are interested, take a look at the portal

The last

If there is not quite reasonable place, I hope to point out in time, more exchanges ~

Talk is always empty, real knowledge comes from practice, check the code please everyone children shoes move to github, don’t forget to click a like yo 👍

Project Address 👉 : github.com/MrLeihe/vue…

The resources

  • MDN: developer.mozilla.org/zh-CN/docs/…
  • MDN:developer.mozilla.org/zh-CN/docs/…
  • Custom command: cn.vuejs.org/v2/guide/cu…