This document has been updated on Github.

In recent days, I found such a thing IntersectionObserver(to be more accurate, it is an asynchronous API provided by the browser native) when I was looking at the JavaScript API, which can listen to an element and determine whether the element is in the current window.

IntersectionObserver interface (IntersectionObserver API) provides a means for developers to asynchronously monitor the intersecting state of a target element with its ancestor or viewport. The ancestor element and viewport are called root.

Good official description!!

To put it simply, IntersectionObserver is to observe whether an element is in the current window.

Note: The API is asynchronous

Method of use

/ * * *@param {Function} Callback is mandatory. It listens for the callback function when an element enters the window@param {Object} Options This parameter is not mandatory@param {Dom Element} Options. root listens for the root element of an element, which defaults to the full document *@param {String} RootMargin Rectangle offset from the element to the root element edge. Px and % * are supported@param {Array} Options. Thresholds Ratio of cross area to boundary area */

var io = new IntersectionObserver(callback, options);

io.observe(document.querySelector('img')); // Start the observation by accepting a DOM node object
io.unobserve(element); // Stop observing to accept an element
io.disconnect(); // Close the observer
Copy the code

Meng? Fortunately, API methods and attributes are relatively few, first a case you understand how to use.

case

<div id='app'>
  <img src='./images/01.png'>
  <img src='./images/01.png' >
  <img src='./images/01.png' >
  <img src='./images/01.png' >
  <img src='./images/01.png' >
  <img src='./images/01.png' >
</div>

<script>
  // Create an instance of IntersectionObserver
  const io = new IntersectionObserver(function (entrys) {
    console.log(entrys);
  }, {threshold: [1]});

  function update () {
    const els = document.querySelectorAll('img');
    els.forEach(ele= > {
      io.observe(ele); // Listen for elements
    });
  }

  update();
</script>
Copy the code

At this point, the console will print out the listening list, and notice that each element in the list is a listener, where the target attribute is the listening element, and isIntersecting is the identifier of whether the element is in the window.

Lazy image loading

First of all, what do we want for lazy loading?

  • 1. Default placeholder map
  • 2,imgElement with a real address attribute, such asdata-src
  • 3. What we just sawoptions.thresholds, namely, the ratio between the crossing region and the boundary region
class InterObser {
  constructor (attr = 'data-src', defaultImg, options = {}) {
    this.attr = attr;
    this.defaultImg = defaultImg;
    this.options = options;
    
    this.install();
    this.update();
  }
  install () {
    const that = this;
    // Initialize the IntersectionObserver instance
    that.io = new IntersectionObserver(function (entrys) {
      // Loop through each listener when an element enters the window
      entrys.forEach(ele= > {
        // If the listener enters the window, replace the placeholder map with the real address
        if (ele.isIntersecting) {
          that.imgLoad(ele);
          // Stop listening on the listener to prevent multiple operationsthat.io.unobserve(ele.target); }}); }, that.options); }// Load the image
  imgLoad (ele) {
    const url = ele.target.dataset.src;
    ele.target.src = url;
  }
  update () {
    const els = document.querySelectorAll(` [The ${this.attr}] `);
    // Set default values for each listener element
    els.forEach(ele= > {
      ele.src = this.defaultImg;
      // Start listening
      this.io.observe(ele); }); }}/ / call
new InterObser('data-src'.'./images/01.png', {threshold: [1]});
Copy the code

Self-created vUE lazy loading plug-in

Since we know that IntersectionObserver is used for lazy image loading, can we make this function into a plug-in? My current project is mainly vUE, so I will make it into a VUE plug-in.

Create the lib/index.js file in the SRC directory.

class InterObser {
  install (Vue, options) {
    const that = this;
    // Create directive using v-lazy='http://xxx'
    Vue.directive('lazy', {
      bind(el, binding) {
        // Add a default placeholder map for the element
        that.init(el, binding.value, options.defaultSrc);
        // Create an instance of IntersectionObserver
        that.initObserve(options);
      },
      inserted (el, binding) {
        that.observe(el, binding); // Enable listening on this element
      }
    })
  }
  init (el, val, def) {
    // Mount the value of v-lazy to the element, because there is no v-lazy attribute on the compiled element
    el.setAttribute('data-src', val)
    // Set the default placeholder
    el.src = def
  }
  initObserve (options) {
    // This only needs to be created once
    var io = new IntersectionObserver(function (entrys) {
      // Loop through each of the listening elements. When the element enters the window, the real address is replaced and the listening is cancelled
      entrys.forEach(entry= > {
        if (entry.isIntersecting) { // If isIntersecting is true, it means that the element enters the window
          const el = entry.target;
          el.src = el.dataset.src; // Replace placeholders with real addresses
          el.removeAttribute('data-src')); // Remove the redundant data-src attribute
          setTimeout(function () {
            io.unobserve(el); // Cancel listening
          }, 0);
        }
      })
    }, options.interOptions || {});

    this.io = io;
  }
  observe (el) {
    this.io.observe(el); }}export default new InterObser();
Copy the code

use

This is configured in main.js

import Vue from 'vue';
import LazyLoad from './lib/index.js';
// Global configuration
Vue.use(LazyLoad, {
  defaultSrc: 'http://localhost:3000/01.jpg'.// IntersectionObserver Native configuration item
  interOptions: {
    threshold: [1]}});Copy the code

Used in components

<template>
  <div>
    <img v-lazy='lazySrc'>
    <img v-lazy='lazySrc'>
    <img v-lazy='lazySrc'>
    <img v-lazy='lazySrc'>
    <img v-lazy='lazySrc'>
  </div>
</template>

<script>
export default {
  data () {
    return {
      lazySrc: 'http://localhost:3000/02.jpg'}; }};</script>
Copy the code

If it is very simple, let’s take a look at how traditional image lazy loading is implemented.

Traditional images load lazily

Traditionally lazy image loading relies on listening for Scroll and getting getBoundingClientRect().top and getBoundingClientRect().bottom, but there are performance issues. For example, getBoundingClientRect will cause reflux and so on. What we can do is reduce these performance operations.

We can optimize the above plug-ins. If the browser does not support IntersectionObserver, we will adopt the traditional lazy loading method.

class InterObser {
  install () {
    .....
  }
  initObserve (options) {
    // There is only one more criterion
    // Initialize the API only if the browser supports it
    if (IntersectionObserver) {
      var io = new IntersectionObserver(function () {... }, options.interOptions || {});this.io = io;
    }
  }
  observe (el) {
    // If the browser supports IntersectionObserver, use it; otherwise, use the traditional lazy loading mode
    if (IntersectionObserver) {
      this.io.observe(el);
    } else {
      this.listenerScroll(el);
    }
  }
  listenerScroll (el) {
    const that = this;
    that.load(el); // In order for the current window image to load properly
    window.addEventListener('scroll'.function (e) {
      // Prevent multiple triggers
      // Reduce the getBoundingClientRect operation to optimize
      if(el.dataset.src) { that.load(el); }}); } load (el) {// Get the window height
    var docHeight = document.documentElement.clientHeight;
    var boundingClientRect = el.getBoundingClientRect();
    var bottom = boundingClientRect.bottom;
    var top = boundingClientRect.top;
    // When the element enters the window, the real image is loaded
    if (top < docHeight && bottom > 0) {
      el.src = el.dataset.src;
      el.removeAttribute('data-src'); }}}Copy the code

legacy

A few questions remain

  • When the user is passed inoptions.interOptionsNo compatibility processing was performed when
  • In addition to the judgeel.dataset.srcIn addition to optimization can also be matched with the throttle function
  • Default image andv-lazyThe images in “can only be web images

These questions will be gradually added later. Personally, I think this plug-in can be improved, and it is not limited to img tag, but also lazy loading of video and so on.


For more documentation, see:

Online address [front Tangerine gentleman]

GitHub Warehouse [Front Tangerine]