连接: https://blog.angularindepth.com/a-modern-solution-to-lazy-loading-using-intersection-observer-9280c149bbchtml
现现在Web应用的性能现在愈来愈重要,有一个影响页面加载的很重要因素就是图片,尤为是页面中有不少图片的时候。若是可能的话,对这些图片使用懒加载是坠吼的,也就是只有当用户滚动到图片位置时才去加载这张图片,这样作的确提高了页面的首次加载速度。对于移动端而言,这样也能够节约用户的流量。typescript
要想知道当前是否须要加载一张图片,咱们须要坚持当前页面可见范围内这张图片是否可见。若是是,则加载。检查方法:咱们能够经过事件和事件处理器来监测页面滚动位置、offset值、元素高度、视窗高度并计算出这张图片是否在可见视窗内。数组
可是,这样作也有几点反作用:浏览器
最近我阅读了一个比较新的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实现的懒加载主要包括如下几个步骤:性能
在Angular中,咱们能够将这些功能放进一个指令里。ui
因为咱们这里须要改变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实例,建立过程接受两个参数:
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等其余框架中使用,原理都是同样的。
结束!哈!