[圖片懶加載][Angular]使用Intersection Observer實現圖片懶加載並在Angular中使用

連接: https://blog.angularindepth.com/a-modern-solution-to-lazy-loading-using-intersection-observer-9280c149bbchtml

現現在Web應用的性能現在愈來愈重要,有一個影響頁面加載的很重要因素就是圖片,尤爲是頁面中有不少圖片的時候。若是可能的話,對這些圖片使用懶加載是墜吼的,也就是隻有當用戶滾動到圖片位置時纔去加載這張圖片,這樣作的確提高了頁面的首次加載速度。對於移動端而言,這樣也能夠節約用戶的流量。typescript

當前懶加載圖片的方法

要想知道當前是否須要加載一張圖片,咱們須要堅持當前頁面可見範圍內這張圖片是否可見。若是是,則加載。檢查方法:咱們能夠經過事件和事件處理器來監測頁面滾動位置、offset值、元素高度、視窗高度並計算出這張圖片是否在可見視窗內。數組

可是,這樣作也有幾點反作用:瀏覽器

  • 因爲全部的計算將在JS的主線程進行,所以可能會帶來性能問題;
  • 每次執行滾動時,以上計算都會執行一遍,若是咱們的圖片在最底部的,無形間浪費了不少資源;
  • 若是頁面中有不少圖片,這些計算將會十分佔用資源。

一個更加現代化的解決方案

最近我閱讀了一個比較新的DOM API,Interction Observer API。這個API提供了一種偵測元素與當前視窗相交的方法,同時當這個元素與視窗開始相交或者相離時能夠觸發執行一個回調函數。所以,咱們就不須要在JS主線程中進行其餘多餘的計算。app

除了偵測元素與視窗是否相交以外,Intersection Observer API還能夠偵測元素與視窗相交的百分比。只須要在建立一個intersection observer時的options中設置threshold參數。threshold參數接受一個0到1。當threshold值爲0時意味着一旦元素的第一個像素出如今視窗中時,回調函數就會被觸發,值爲1時則是元素徹底顯示時纔會觸發回調函數。框架

threshold也能夠是一個由0到1之間的數組成的數組,這樣每當圖片與視窗相交範圍達到這個值時,回調函數就會被觸發。codepen這裏有一個案例,解釋了threshold數組是如何工做的。函數

總的來講,經過Intersection Observer API實現的懶加載主要包括如下幾個步驟:性能

  • 建立一個intersection observer實例;
  • 經過這個實例能夠觀測到咱們但願懶加載的元素的可見狀況;
  • 當元素出如今視窗中,加載元素;
  • 一旦元素加載完成,則中止對他的觀測;

在Angular中,咱們能夠將這些功能放進一個指令裏。ui

將以上功能封裝成一個Angular指令

因爲咱們這裏須要改變DOM元素,所以咱們能夠封裝一個Angular指令做用於咱們想懶加載的元素上。this

這個指令會有一個輸出事件,這個事件會在元素出如今視窗後觸發,在咱們的場景下,這個事件是顯示這個元素;

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, ) {}
}
複製代碼

建立一個intersection observer並開始觀察元素

組件視圖初始化成功後,咱們須要建立一個intersection observer實例,建立過程接受兩個參數:

  • 一個元素與視窗相交百分比達標後觸發的回調函數
  • 一個可選的對象options
ngAfterViewInit() {
    this._intersectionObserver = new IntersectionObserver(entries => {
      this._chechForIntersection(entries);
    }, {});
    this._intersectionObserver.observe(<Element>this._elemRef.nativeElement);
  }

private _chechForIntersection(entries: IntersectionObserverEntry[]) {}
複製代碼

偵測,加載,取消觀察

回調函數_chechForIntersection()應該在偵測到元素與視窗相交後執行,包括向外emit一個方法deferLoad,取消觀察元素,斷開這個intersection observer。

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

            // 取消觀察元素,斷開這個intersection observer
            this._intersectionObserver.unobserve(this._elemRef.nativeElement);
            this._intersectionObserver.disconnect();
        }
    });
}

private _checkIfIntersecting(entry: IntersectionObserverEntry) {
    return entry.isIntersecting && entry.target === this._elemRef.nativeElement;
}
複製代碼

使用

將directive在模塊中導入,並在declarations中聲明;

<div appDeferLoad (deferLoad)="showMyElement=true">
    <my-element *ngIf=showMyElement>
      ...
    </my-element>
</div>
複製代碼

這樣就會給這個div加上延遲加載,並在顯示後觸發(deferLoad)中的方法。經過這個方法咱們能夠控制元素的顯示隱藏

總結

完整的指令以下所示

// 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();

        // 取消觀察元素,斷開這個intersection observer
        this._intersectionObserver.unobserve(this._elemRef.nativeElement);
        this._intersectionObserver.disconnect();
      }
    });
  }

  private _checkIfIntersecting(entry: IntersectionObserverEntry) {
    return (<any>entry).isIntersecting && entry.target === this._elemRef.nativeElement;
  }

}
複製代碼

最後了最後了

這個API還處於WD(working draft)階段,對於不支持的瀏覽器例如IE全系列,EDGE15如下版本,咱們仍須要使用文章開頭提到的方案。固然,本文只是實現了一個Intersection onserver在Angular應用中的使用,一樣你也能夠在React,Vue等其餘框架中使用,原理都是同樣的。

結束!哈!

相關文章
相關標籤/搜索