preface

For the website with more pictures, if all pictures are loaded at one time, on the one hand, due to the loading of more pictures at the same time, the DOM elements of the page will be very many, which will cause a serious decline in page performance, and the pressure of the server will also be very great. On the other hand, if a lot of pictures are loaded, and users browse only a few pictures, it will consume a lot of traffic, resulting in waste.

Lazy loading is optimized for this situation, and will greatly improve the user experience. To sum up, lazy loading is lazy loading, loading images when they are needed.

offsetTop

Lazy loading pictures are usually fixed in width and height. In order to avoid stretching when the picture is larger, object-fit: Cover can be used to cut.

<style>
  img {
    display: block;
    margin-bottom: 10px;
    width: 100%;
    height: 200px;
    object-fit: cover;
  }

  body {
    margin: 0;
  }
</style>

<img data-src="1.jpg" alt="">
<img data-src="2.jpg" alt="">
<img data-src="3.jpg" alt="">
<img data-src="4.jpg" alt="">
<img data-src="5.jpg" alt="">
<img data-src="6.jpg" alt="">
<img data-src="7.jpg" alt="">
Copy the code

LoadImg is used to load the image SRC attribute.

Frequent scrolling of the scrollbar can affect browser performance, so the Debounce function is packaged to limit the frequency of the trigger. Note that debounce cannot be an arrow function, which would cause this to point to an object that is bound to an event, such as el.addeventListener (event, fn). Fn should point to el.

In theory, images can be loaded when they are located in the viewport. However, in order to improve user experience, images can be loaded in advance when they are a fixed distance from the viewport. Therefore, the offset variable is defined globally.

Scrollbar height

The lazyLoad function, the window. The innerHeight as the viewport height, the document. The documentElement. ScrollTop and document. The body. The scrollTop is the scroll bar rolling distance, The difference depends mainly on whether the document declares a DOCType.

way type Chrome Firefox IE11 IE10 IE9
HTMLThe document statementdoctype document.documentElement.clientHeight available available available available available
document.body.scrollTop 0 0 0 0 0
HTMLDocument undeclareddoctype document.documentElement.clientHeight 0 0 available available available
document.body.scrollTop available available available available 0

Can be observed the document. The documentElement. ScrollTop and document body. The scrollTop there is always a can get rolling distance, So you can document. DocumentElement. ScrollTop | | document. The body. The scrollTop to compatible.

<script>
  const loadImg = el= > {
    if(! el.src) { el.src = el.dataset.src } }const debounce = (fn, delay = 100) = > {
    var timer = null

    return function (. args) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }

      timer = setTimeout(() = > {
        fn.call(this. args) }, delay) } }const imgs = document.querySelectorAll('img')

  const offset = 20

  var loaded = 0

  const lazyLoad = () = > {
    const clientHeight = window.innerHeight
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop

    for (var i = loaded; i < imgs.length; i++) {
      if (imgs[i].offsetTop <= clientHeight + scrollTop + offset) {
        loadImg(imgs[i])
        loaded++
      } else {
        break
      }
    }
  }

  lazyLoad()

  window.addEventListener('scroll', debounce(lazyLoad, 200))
</script>
Copy the code

The loaded variable

In addition, the loaded variable is defined globally to store the index that the image is about to load, so as to avoid traversing from the first image every time.

The if statement in the for loop is the key part. As long as the image offset attribute is less than the sum of viewport height, scrolling distance and offset value, the image must be loaded. If a certain image does not meet the loading conditions, the subsequent images must also not meet the loading conditions, so break terminates the loop in advance.

getBoundingClientRect

GetBoundingClientRect returns the size of the element and its position relative to the viewport.

Browsers are compatible with Chrome, Firefox, and Internet Explorer 5 or later.

Standard box model, element width/height + padding + border-width sum. If the CSS attribute is box-sizing: border-box, the element size is width/height.

#img {
  display: block;
  margin-bottom: 10px;
  width: 300px;
  height: 200px;
  border: 10px solid lightblue;
  padding: 20px;
}

<img id="img" src="image.png" alt="">

const img = document.getElementById('img')
console.log(img.getBoundingClientRect())
Copy the code

Browser differences

Print parameters in Chrome.

Internet Explorer 8 prints parameters. Notice that objects returned by Internet Explorer 8 or below do not contain width and height attributes.

IE7 prints the parameters. Note that the coordinates of HTML elements on the page in IE7 are calculated from (2, 2).

Therefore, packaged as a tool function, compatible with IE7 and later browsers.

function getBoundingClientRect(el) {
  var rect = el.getBoundingClientRect()
  var l = document.documentElement.clientLeft
  var t = document.documentElement.clientTop

  return {
    left: rect.left - l,
    right: rect.right - l,
    bottom: rect.bottom - t,
    top: rect.top - t,
    width: rect.right - rect.left,
    height: rect.bottom - rect.top,
  }
}
Copy the code

According to this tool function, the lazy offsetTop mode is modified slightly.

const lazyLoad = () = > {
  for (var i = loaded; i < imgs.length; i++) {
    if (getBoundingClientRect(imgs[i]).top <= window.innerHeight + offset) {
      loadImg(imgs[i])
      loaded++
    } else {
      break}}}Copy the code

IntersectionObserver

IntersectionObserver is a browser-provided constructor that creates an observer instance, for details.

const io = new IntersectionObserver(callback, options)
Copy the code

This example provides part of the method.

  • io.observe(): Starts observation. The parameter is aDOMThe node object
  • io.unobserve(): To cancel observation, the parameter can beDOMNode object, also do not pass
  • io.disconnect(): Closes the observer

Consider the callback function, which is a viewport to view one or more elements, and the callback usually fires twice, once when the observed element enters the viewport and once when the observed element leaves the viewport entirely.

const io = new IntersectionObserver((entries, observer) = >{})Copy the code

Observer refers to IntersectionObserver instance to be invoked, that is, the IO instance mentioned above.

IntersectionObserverEntry

Entries is a IntersectionObserverEntry object array. If Windows looked at three elements, the entries within the array will have three instances, and all is IntersectionObserverEntry object.

Under the Chrome IntersectionObserverEntry objects including eight properties.

  • boundingClientRect: Rectangle information about the observed element, which is executed by the observed elementel.getBoundingClientRect()Return result of
  • intersectionRect: Rectangle information about the area where the observed element intersects the viewport (or root element)
  • intersectionRatio: Intersection ratio, that isintersectionRectAccount forboundingClientRectThe proportion of the area, when the element being observed is fully visible1Is completely invisible0
  • isIntersecting: Indicates whether the observed element is visible in the viewport, if yestrue
  • rootBounds: Rectangle information for the root element. If no root element is specified, rectangle information for the window
  • target: The observed element is aDOMnode
  • time: High precision timestamp, in milliseconds. Says from theIntersectionObserverFrom 0 to 0callbackThe length of time between the two when triggered

Options, the second argument to the constructor IntersectionObserver, is an object that contains three properties.

  • threshold: trigger the callback function when the visible part of the observed element is in the viewport.thresholdIs an array, default is[0]

Trigger the callback function when 0%, 50%, 75%, 100% of the observed elements below are visible

new IntersectionObserver(callback, {
  threshold: [0.0.5.0.75.1],})Copy the code
  • root: In addition to viewing viewport elements, you can also specify root elements

When multiple Li’s are rolled within the following UL element, the ul is triggered when a LI appears.

<style>
  ul {
    width: 300px;
    height: 100px;
    overflow: auto;
  }

  li {
    height: 24px;
    background-color: #ccc;
    margin-bottom: 1px;
  }

  li:nth-of-type(9) {
    background-color: lightblue;
  }
</style>

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
  <li>9</li>
  <li>10</li>
</ul>
<script>
  const ul = document.querySelector('ul')
  const li = document.querySelectorAll('li') [8]
  const callback = entries= > {
    console.log(entries)
  }
  const io = new IntersectionObserver(callback, {
    root: ul,
  })

  io.observe(li)
</script>
Copy the code

Note that the root element must be the ancestor of the element being observed.

  • rootMargin: defines the window or root elementmarginFor extensionrootBoundsThe size of the region. The default value is"0px 0px 0px 0px"

The following window has been expanded to a red area. Generally, the observed element is triggered only when it appears in the window (or at a specified scale). If the observed element is triggered at a fixed distance from the window, rootMargin can be used.

implementation

Now look at the lazy loading of images, less code, first look at the code.

<script>
  const loadImg = el= > {
    if(! el.src) { el.src = el.dataset.src } }const offset = 20

  const imgs = document.querySelectorAll('img')

  const callback = (entries, i) = > {
    entries.forEach(el= > {
      if (el.isIntersecting) {
        loadImg(el.target)
        io.unobserve(el.target)
      }
    })
  }

  const io = new IntersectionObserver(callback, {
    rootMargin: `0px 0px ${offset}px 0px`,
  })

  imgs.forEach(img= > io.observe(img))
</script>
Copy the code

The observer IO is created, defaulting to viewport because the root element is not specified, and viewport traverses the IMG element.

As with offsetTop, the image is loaded 20px away from the viewport. So add the rootMargin configuration item.

The callback function loads the image as long as the element appears in the viewport, while unobserve unwatches the corresponding IMG element.

compatibility

This is fully usable with browsers such as Chrome or Firefox, but is not compatible with IE9-11. Use intersection-observer-polyfill to make waves compatible.

Note that Internet Explorer does not support object-fit, but that is not the point.

<script src="IntersectionObserver.js"></script>
<style>
  img {
    display: block;
    margin-bottom: 10px;
    width: 100%;
    height: 200px;
    /* object-fit: cover; * /
  }

  body {
    margin: 0;
  }
</style>

<script>
  var loadImg = function (el) {
    if(! el.src) { el.src = el.getAttribute('data-src')}}var offset = 20

  var imgs = document.getElementsByClassName('aaa')

  var callback = function (entries, i) {
    entries.forEach(function (el) {
      if (el.isIntersecting || el.intersectionRatio > 0) {
        loadImg(el.target)
        io.unobserve(el.target)
      }
    })
  }

  var io = new IntersectionObserver(callback, {
    rootMargin: '0px 0px ' + offset + 'px 0px',})for (var i = 0; i < imgs.length; i++) {
    io.observe(imgs[i])
  }
</script>
Copy the code

Effect in Internet Explorer 9.

🎉 is at the end

🍻 fellows, if you have seen this article and think it is helpful to you, please like 👍 or Star ✨ to support it!

Manual code word, if there is an error, welcome to the comment area correction 💬~

Your support is my biggest motivation to update 💪~

GitHub/Gitee, GitHub Pages, Nuggets, CSDN updates, welcome to pay attention to 😉~