[图片懒加载][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等其余框架中使用,原理都是同样的。

结束!哈!

相关文章
相关标签/搜索