This article is featured on Github github.com/Michael-lzg…

Demo source address github.com/Michael-lzg…

In e-commerce projects, there are often a large number of pictures, such as banner advertising picture, menu navigation picture, meituan and other business list head picture. The large number of pictures and the large size of pictures often affect the page loading speed, resulting in a bad user experience, so it is imperative to optimize the lazy loading of pictures.

Why lazy image loading

Let’s take a look at the image information that loads when the page launches.

, as shown on this page load when starting a few images (or more), and these pictures request is almost simultaneous, in Chrome, maximum number of concurrent requests are limited and the request of the other will be pushed to the queue waiting or stagnate, until last round after the completion of the new request. Therefore, a considerable number of image resource requests require queuing time.

As can be seen from the above, some pictures reach hundreds of kB, and the setting is 2M. , directly resulting in a long loading time.

In view of the above situation, image lazy loading has the following advantages:

  1. Reduce the load of resources, the page only load the first screen image, this can significantly reduce the server pressure and traffic, also can reduce the burden of the browser.
  2. Prevent the concurrent loading of resources too much and block JS loading, affecting the start of the entire site.
  3. It can improve user experience. Imagine that when users open a page, if all the pictures on the page need to be loaded, due to the large number of pictures, the waiting time will be long, which will seriously affect user experience.

Image lazy loading principle

The principle of picture lazy loading is mainly to judge whether the current picture has reached the visual area of the core logic

  1. Get all the pictures dome.
  2. Walk through each image to determine if the current image is in the viewable area.
  3. If it does, set the image’s SRC property.
  4. The binding of windowscrollEvent to listen for events.

Let’s take a look at the page structure first

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Lazyload</title>
    <style>
      img {
        display: block;
        margin-bottom: 50px;
        height: 200px;
        width: 400px;
      }
    </style>
  </head>
  <body>
    <img src="./img/default.png" data-src="./img/1.jpg" />
    <img src="./img/default.png" data-src="./img/2.jpg" />
    <img src="./img/default.png" data-src="./img/3.jpg" />
    <img src="./img/default.png" data-src="./img/4.jpg" />
    <img src="./img/default.png" data-src="./img/5.jpg" />
    <img src="./img/default.png" data-src="./img/6.jpg" />
    <img src="./img/default.png" data-src="./img/7.jpg" />
    <img src="./img/default.png" data-src="./img/8.jpg" />
    <img src="./img/default.png" data-src="./img/9.jpg" />
    <img src="./img/default.png" data-src="./img/10.jpg" />
  </body>
</html>
Copy the code

First to get all the pictures of the dom, through the document. The body. The clientHeight get highly visual area, then use element. GetBoundingClientRect () API directly to get the top value of the element relative to browse, Walk through each image to determine if the current image is in the viewable area. The code is as follows:

function lazyload() {
  let viewHeight = document.body.clientHeight // Get the viewable height
  let imgs = document.querySelectorAll('img[data-src]')
  imgs.forEach((item, index) = > {
    if (item.dataset.src === ' ') return

    // Get the left, up, right, and down positions of an element on the page relative to the browser window
    let rect = item.getBoundingClientRect()
    if (rect.bottom >= 0 && rect.top < viewHeight) {
      item.src = item.dataset.src
      item.removeAttribute('data-src')}}}Copy the code

Finally, bind the onScroll event to the window

window.addEventListener('scroll', lazyload)
Copy the code

The main completion of an image lazy loading operation. However, there is a big performance problem, because the Scroll event will trigger many times in a very short time, which seriously affects the page performance. In order to improve the page performance, we need a throttling function to control the function trigger many times, and only perform one callback in a period of time (such as 200ms).

Let’s implement a throttling function

function throttle(fn, delay) {
  let timer
  let prevTime
  return function (. args) {
    const currTime = Date.now()
    const context = this
    if(! prevTime) prevTime = currTimeclearTimeout(timer)

    if (currTime - prevTime > delay) {
      prevTime = currTime
      fn.apply(context, args)
      clearTimeout(timer)
      return
    }

    timer = setTimeout(function () {
      prevTime = Date.now()
      timer = null
      fn.apply(context, args)
    }, delay)
  }
}
Copy the code

Then modify the SRCOLl event

window.addEventListener('scroll', throttle(lazyload, 200))
Copy the code

IntersectionObserver

Through the implementation of the above example, if we want to realize lazy loading, we need to listen for scroll events. Although we can prevent high frequency function execution by function throttling, we still need to calculate the properties of scrollTop, offsetHeight and so on. Is there a simple way not to calculate these attributes? The answer is IntersectionObserver.

IntersectionObserver is a new API that automatically “observes” whether elements are visible and 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.” Let’s see how it’s used:

var io = new IntersectionObserver(callback, option)

// Start observing
io.observe(document.getElementById('example'))

// Stop observing
io.unobserve(element)

// Close the viewer
io.disconnect()
Copy the code

IntersectionObserver is a constructor provided natively by the browser and accepts two parameters: callback is the callback function when visibility changes and option is the configuration object (this parameter is optional).

When the visibility of the target element changes, the observer’s callback function is called. Callback typically fires twice. Once the target element has just entered the viewport (becoming visible), and once it has completely left the viewport (becoming invisible).

var io = new IntersectionObserver((entries) = > {
  console.log(entries)
})
Copy the code

The parameters of the callback function (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.

  • Time: indicates the time when visibility changes. It is a high-precision timestamp in milliseconds
  • Target: The observed target element, which is a DOM node object
  • IsIntersecting: Whether the target is visible
  • RootBounds: Information about the rectangular region of the root element,getBoundingClientRect()Method, or null if there is no root element (that is, scrolling directly relative to the viewport)
  • BoundingClientRect: Information about the rectangular region of the target element
  • IntersectionRect: information on the intersection area between target element and viewport (or root element)
  • IntersectionRatio: intersectionRatio of target elements, i.eintersectionRectboundingClientRectIs 1 when completely visible and less than or equal to 0 when completely invisible

Next, we use IntersectionObserver to achieve lazy image loading

const imgs = document.querySelectorAll('img[data-src]')
const config = {
  rootMargin: '0px'.threshold: 0,}let observer = new IntersectionObserver((entries, self) = > {
  entries.forEach((entry) = > {
    if (entry.isIntersecting) {
      let img = entry.target
      let src = img.dataset.src
      if (src) {
        img.src = src
        img.removeAttribute('data-src')}// Remove the observation
      self.unobserve(entry.target)
    }
  })
}, config)

imgs.forEach((image) = > {
  observer.observe(image)
})
Copy the code

Lazy loading instruction

In addition to the commonly used V-show, V-bind, V-for commands, Vue can also be customized commands. The Vue directive definition function provides several hook functions (optionally) :

  • Bind: called once, when the directive is first bound to an element. You can define an initialization action that is performed once at binding time.
  • Inserted: Called when the bound element is inserted into its parent (the parent is called if it exists, not in the document).
  • Update: Called when the template to which the element is bound is updated, regardless of whether the binding value changes. By comparing the binding values before and after the update.
  • ComponentUpdated: Called when the template to which the bound element belongs completes an update cycle.
  • Unbind: Called only once, when an instruction is unbound from an element.

Implement a lazy load instruction train of thought

  1. Determine whether the browser supports itIntersectionObserverAPI, use it if supportedIntersectionObserverImplement lazy loading, otherwise usesrcollEvent monitoring + throttling method implementation.
  2. throughVue.directiveSign up for av-lazyTo expose oneinstall()Function to be called by Vue.
  3. inmain.jsUse (instruction) can be called.
  4. In the component<img>Of the labelsrcSwitch tov-lazyCan realize lazy loading pictures.

The following code

Create a new lazyload.js file

const LazyLoad = {
  / / install method
  install(Vue, options) {
    const defaultSrc = options.default
    Vue.directive('lazy', {
      bind(el, binding) {
        LazyLoad.init(el, binding.value, defaultSrc)
      },
      inserted(el) {
        if (IntersectionObserver) {
          LazyLoad.observe(el)
        } else {
          LazyLoad.listenerScroll(el)
        }
      },
    })
  },
  / / initialization
  init(el, val, def) {
    el.setAttribute('data-src', val)
    el.setAttribute('src', def)
  },
  // Use IntersectionObserver to monitor EL
  observe(el) {
    var io = new IntersectionObserver((entries) = > {
      const realSrc = el.dataset.src
      if (entries[0].isIntersecting) {
        if (realSrc) {
          el.src = realSrc
          el.removeAttribute('data-src')
        }
      }
    })
    io.observe(el)
  },
  // Listen for scroll events
  listenerScroll(el) {
    const handler = LazyLoad.throttle(LazyLoad.load, 300)
    LazyLoad.load(el)
    window.addEventListener('scroll'.() = > {
      handler(el)
    })
  },
  // Load the real image
  load(el) {
    const windowHeight = document.documentElement.clientHeight
    const elTop = el.getBoundingClientRect().top
    const elBtm = el.getBoundingClientRect().bottom
    const realSrc = el.dataset.src
    if (elTop - windowHeight < 0 && elBtm > 0) {
      if (realSrc) {
        el.src = realSrc
        el.removeAttribute('data-src')}}},/ / throttling
  throttle(fn, delay) {
    let timer
    let prevTime
    return function (. args) {
      const currTime = Date.now()
      const context = this
      if(! prevTime) prevTime = currTimeclearTimeout(timer)

      if (currTime - prevTime > delay) {
        prevTime = currTime
        fn.apply(context, args)
        clearTimeout(timer)
        return
      }

      timer = setTimeout(function () {
        prevTime = Date.now()
        timer = null
        fn.apply(context, args)
      }, delay)
    }
  },
}

export default LazyLoad
Copy the code

Use directive in main.js

import LazyLoad from './LazyLoad.js'

Vue.use(LazyLoad, {
  default: 'xxx.png',})Copy the code

Change the SRC of the tag in the component to V-lazy

<img v-lazy="xxx.jpg" />
Copy the code

This completes a vue lazy loading command.

summary

  1. Lazy loading of images is necessary to improve site loading performance.
  2. The implementation principle of lazy image loading is to judge whether the current image is loaded in the visible area. The function can be realized by monitoring scroll events and IntersectionObserver.
  3. You can use vue. directive to write lazy image loading directives.

Recommend the article

W You must know the webPack plugin principle analysis
Asynchronous loading principle and subcontracting strategy of Webpack
Summary of 18 Webpack plugins, there’s always something you want!
Build a VuE-CLI4 + WebPack mobile framework (out of the box)
Build from scratch to optimize a vue-CLI-like scaffolding
Encapsulate a TOAST and Dialog component and publish it to NPM
Build a WebPack project from scratch
Summary of several webpack optimization methods
Summary of advanced application of VUE knowledge system
Summarize the practical skills of vUE knowledge system
Summarize the basic introduction to vUE knowledge system
Summary of mobile H5 development tips.