利用代理模式和Intersection Observer实现图片懒加载

155 阅读4分钟

代理模式

代理模式

代理模式就是给对象提供一个代用品或占位符,以控制对它的访问

举个🌰

小明暗恋女神小芳很久了,情人节终于鼓起勇气了,买了束花,想向小芳进行表白,就像如下流程:

image.png

let Flower = function(){};

let xiaoming = {
    sendFlower: function(target){
      const flower = new Flower();
      target.receiveFlower(flower);
    }
};

let xiaofang = {
  receiveFlower: function(flower){
   console.log('已收到', flower);
  }
}

xiaoming.sendFlower(xiaofang)

但在送花途中,小明一直犹豫不决,仍是有点胆怯,正在这时,遇见了小芳的好闺蜜小红,小明仿佛像抓住了救命稻草一般,让小红帮忙代送,此时流程如下:

image.png

let Flower = function(){};

let xiaoming = {
    sendFlower: function(target){
      const flower = new Flower();
      target.receiveFlower(flower);
    }
};

let xiaohong = {
  receiveFlower: function(flower){
   xiaofang.receiveFlower(flower);
  }
}

let xiaofang = {
  receiveFlower: function(flower){
   console.log('已收到', flower);
  }
}

xiaoming.sendFlower(xiaofang)

这就是代理模式的概念了,我们将一件事交给他人去做,以便达到某种目的/解决某种问题。 然而代理模式实际上又分为两种情况: 虚拟代理和保护代理。

虚拟代理

虚拟代理: 先占个位,将一些开销很大的对象,延迟到真正需要它的时候去进行创建

我们还是继续送花的🌰:小红收到花后想了想,觉得小芳最近心情不好,想到小芳心情好的时候再送花,这就是虚拟代理

保护代理

保护代理 代理时进行保护,将需要代理的方法或者对象处理成想要的结果,然后再传递给最终的对象。

小红觉得花中有一些瑕疵,需要重新包裹下,然后再送给小芳,这就是保护代理

Intersection Observer

Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法,说人话就是可以检测元素是否进入视口,我们常用来做图片的懒加载、进入某个区域时执行任务或播放动画等

属性

  • IntersectionObserver.root 

    所监听对象的具体祖先元素 (element)。如果未传入值或值为null,则默认使用顶级文档的视窗。意思就是我们在哪里的根元素内进行监听。比如图片懒加载,当图片进入视口时我们才去进行获取对应的链接,图片进入视口,相当于监听图片是否在视口内,这个视口就是根元素

  • IntersectionObserver.rootMargin 

    计算交叉时添加到根 (root)边界盒bounding box (en-US)的矩形偏移量,可以有效的缩小或扩大根的判定范围从而满足计算需要。所有的偏移量均可用像素 (px) 或百分比 (%) 来表达,默认值为"0px 0px 0px 0px"。相当于设置root的margin,从而扩大/缩小判定范围

  • IntersectionObserver.thresholds 只读

    一个包含阈值的列表,按升序排列,列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会生成一个通知 (Notification)。如果构造器未传入值,则默认值为 0。

方法

图片懒加载总思路

为了遵循单一职责原则,同时也为了复用,我们应将懒加载设置为一个单独的方法(angular中可使用service)实现(代理模式),而当图片进入视口时先加载占位图(虚拟代理),当请求图片返回时,再使用原图片资源覆盖占位图

图片懒加载具体实现

 private _qfLazyPlaceholder: string | boolean;

 // 是否展示懒加载占位图
  @Input()
  get qfLazyPlaceholder(): string | boolean {
    return this._qfLazyPlaceholder;
  }

  set qfLazyPlaceholder(value: string | boolean) {
    this._qfLazyPlaceholder = value === '' || value;
  }
  
  //图片指令初始化
  ngOnInit(): void {
    this.proceeLazyPlaceHolder();
  //propertyChanges可以理解为每次不同图片的加载,
  //lazyload就是懒加载服务,intoView是判断元素是否进入视口的方法,元素进入视口,加载完对应的src,
  //我们就去更新一遍src
  //hostElement就是对应的图片标签元素
    this.propertyChanges$
      .pipe(
        mergeMap(() => (this.lazyLoad.intoView(this.hostElement)),
        map(() => this.src),
      )
      .subscribe(src => {
      //这边用到了rxjs,每个propertyChanges都会像水流一样流经pipe的处理再进入更新图片src的方法
        this.updateImgSrc(src);
      });
  }
  
    //先去加载占位图,假如没有就直接跳出
    private proceeLazyPlaceHolder() {
    if (this.qfLazyPlaceholder === undefined) {
      return;
    }
    let src: string = FALLBACK_IMG;
    if (typeof this.qfLazyPlaceholder === 'string' && this.qfLazyPlaceholder !== '') {
      src = this.qfLazyPlaceholder;
    }
    //将占位图传入src,进行虚拟代理
    this.updateImgSrc(src);
    }
    
    
    //懒加载服务的方法,写法千人千面,不再陈述细节,总结就是利用intersectionObserver过滤出进入视口的图片
    intoView(element: HTMLElement, threshold: number = 0.1): Observable<boolean> {
    let observer_: Subscriber<boolean>;
    let intersectionObserver: IntersectionObserver;

    return new Observable(observer => {
      const options = {
        root: null,
        threshold,
      };

      observer_ = observer;
      intersectionObserver = new IntersectionObserver(entries => {
        observer.next(entries);
      }, options);

      intersectionObserver.observe(element);
    }).pipe(
      mergeMap((entries: IntersectionObserverEntry[]) => entries),
      filter(entry => entry.isIntersecting),
      tap(() => intersectionObserver.disconnect()),
      map(entry => entry.isIntersecting),
      tap(() => observer_.complete()),
    );
  }
  

以上就是本文全部内容啦,大家如果觉得有用的话,帮忙点个赞转发一下吧