Link: https://blog.angularindepth.com/a-modern-solution-to-lazy-loading-using-intersection-observer-9280c149bbc

As Web application performance becomes more and more important today, one of the most important factors affecting page loading is images, especially if there are many images on a page. If possible, using lazy loading on these images is slack-roaring, meaning that the image is loaded only when the user has scrolled to the image location, which does increase the first load speed of the page. For mobile terminals, this can also save users’ traffic.

The current lazy method for loading images

To know if we need to load an image at the moment, we need to insist that the image is visible on the current page. If so, load it. Check method: We can use the event and event handler to monitor the page scroll position, offset value, element height, window height and calculate whether the image is in the visible window.

However, there are several side effects:

  • Since all of the calculations will be done on the main thread of JS, there may be performance issues;
  • The above calculation is performed every time the scroll is performed. If our image is at the bottom, a lot of resources are wasted.
  • If the page has a lot of images, these calculations can be very resource-intensive.

A more modern solution

I recently read the Interception Observer API, a new DOM API. The API provides a way to detect an element’s intersection with the current window and trigger a callback when the element begins to intersect or diverge from the window. Therefore, we do not need to do any extra calculations in the JS main thread.

In addition to detecting whether an element intersects the window, the Intersection Observer API can also detect the percentage of elements intersecting the window. Just set the threshold parameter in options when creating a Intersection Observer. The threshold parameter takes a 0 to 1. A threshold value of 0 means that the callback function is triggered when the first pixel of the element appears in the window, and a threshold value of 1 means that the callback function is triggered when the element is fully displayed.

Threshold can also be an array of numbers between 0 and 1, so that whenever the image intersects the window at this point, the callback function is triggered. Codepen Here is an example that explains how the Threshold array works.

In general, lazy loading via the Intersection Observer API consists of the following steps:

  • Create an instance of intersection Observer.
  • From this example we can observe the visibility of the elements we want to lazily load;
  • Load the element when it appears in the window.
  • Once the element is loaded, stop observing it;

In Angular, we can put these functions into a directive.

Encapsulate the above functionality as an Angular directive

Since we need to change the DOM element here, we can wrap an Angular directive around the element we want to load lazily.

This instruction will have an output event that fires when the element appears in the window, which in our case displays the element;

import { Directive, EventEmitter, Output, ElementRef } from '@angular/core';

@Directive({
  selector: '[appDeferLoad]'
})
export class DeferLoadDirective {
  @Output() deferLoad: EventEmitter<any> = new EventEmitter();

  private_intersectionObserver? : IntersectionObserver;constructor(
    private _elemRef: ElementRef,
  ) {}}Copy the code

Create a Intersection Observer and start observing elements

After the component view is successfully initialized, we need to create an instance of the Intersection Observer that takes two parameters:

  • The callback function that is triggered when an element intersects a viewport by a certain percentage
  • An optional object, Options
ngAfterViewInit() {
    this._intersectionObserver = new IntersectionObserver(entries= > {
      this._chechForIntersection(entries);
    }, {});
    this._intersectionObserver.observe(<Element>this._elemRef.nativeElement);
  }

private _chechForIntersection(entries: IntersectionObserverEntry[]) {}
Copy the code

Detect, load, cancel watch

The _chechForIntersection() callback should be executed when it detects an element intersecting the viewport, including discharging a method deferLoad to unobserve the element and disconnect the Intersection Observer.

private _chechForIntersection(entries: IntersectionObserverEntry[]) {
    entries.forEach((entry: IntersectionObserverEntry) = > {
        if (this._checkIfIntersecting(entry)) {
            this.deferLoad.emit();

            // Unobserve the element and disconnect the Intersection Observer
            this._intersectionObserver.unobserve(this._elemRef.nativeElement);
            this._intersectionObserver.disconnect(); }}); }private _checkIfIntersecting(entry: IntersectionObserverEntry) {
    return entry.isIntersecting && entry.target === this._elemRef.nativeElement;
}
Copy the code

use

Import the directive into the module and declare it in the declarations;

<div
    appDeferLoad
    (deferLoad) ="showMyElement=true">
    <my-element
       *ngIf=showMyElement>.</my-element>
</div>
Copy the code

This will add lazy loading to the div and trigger the method in deferLoad after rendering. With this method we can control the display and hiding of elements

conclusion

The complete instructions are shown below

// defer-load.directive.ts
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[appDeferLoad]'
})
export class DeferLoadDirective implements AfterViewInit {

  @Output() deferLoad: EventEmitter<any> = new EventEmitter();

  private _intersectionObserver: IntersectionObserver;

  constructor(
    private _elemRef: ElementRef
  ) { }

  ngAfterViewInit() {
    this._intersectionObserver = new IntersectionObserver(entries= > {
      this._checkForIntersection(entries);
    }, {});
    this._intersectionObserver.observe(<Element>this._elemRef.nativeElement);
  }

  private _checkForIntersection(entries: IntersectionObserverEntry[]) {
    console.log(entries);
    entries.forEach((entry: IntersectionObserverEntry) = > {
      if (this._checkIfIntersecting(entry)) {
        this.deferLoad.emit();

        // Unobserve the element and disconnect the Intersection Observer
        this._intersectionObserver.unobserve(this._elemRef.nativeElement);
        this._intersectionObserver.disconnect(); }}); }private _checkIfIntersecting(entry: IntersectionObserverEntry) {
    return (<any>entry).isIntersecting && entry.target === this._elemRef.nativeElement; }}Copy the code

At last at last

This API is still in the WD(Working Draft) stage, and for unsupported browsers such as the full family of Internet Explorer, EDGE15 and below, we will still need to use the solution mentioned at the beginning of this article. Of course, this article only implements the Intersection onServer in Angular applications. You can also use it in other frameworks like React, Vue, etc. The principle is the same.

The end! Ha! Ha!