likes
comments
collection
share

懒加载、触底加载和预加载

作者站长头像
站长
· 阅读数 22

我们知道我们在实际开发中,我们有些场景会遇见页面的触底加载,或者当要请求的资源很多的时候,为了提高页面性能,让页面更快的展示内容,很可能需要一些懒加载。我最开始学习前端的时候,就是学的比较笨的方法,使用scroll事件监听,然后使用box.getBoundClientRect().topwindow.innerHeight进行比较。(还有更麻烦的方法:box.offsetTop-body.scrollTop的值与window.innerHeight或者body.clientHeight).

...
<img id="#img" data-src='https://xxxxxxxx.jpg'></img>
...
<script>
    window.addEventListener('scroll',function() {
        if(img.getBoundingClientRect().top<window.innerHeight) {
            let src = img.getAttribute('data-src');
            img.setAttribute('src',src);
        }
    })
</script>

但是我们也会发现一个问题,那就是给window添加scroll事件后,会频繁触发这个事件,会导致性能变差,会消耗很多资源。所以后面我又学到了新的方法,那就是使用 IntersectionObserver 这个新的API。

IntersectionObserver介绍

它是一个构造函数,可以创建一个对象,这个对象用于监视某个(某些)节点。在创建这个对象的时候,可以传递两个参数:第一个参数是一个回调函数,当监听的节点对象达到某个触发条件时就会触发,第二个参数是可选的,是一个配置参数,用于改变创建对象的一些属性(不传就使用默认)。

let observe = new IntersectionObserver(callback,options);

这个对象上有几个方法(在其IntersectionObserver构造函数的原型上),分别是:

  • observe:对元素target添加监听,当target元素变化时,就会触发上述的回调
  • unobserve:移除一个监听,移除之后,target元素的可视区域变化,将不再触发前面的回调函数
  • disconnect:停止所有的监听
  • takeRecords:返回一个对象数组,每个对象包含目标元素与根每次的相交信息。

备注:  如果使用回调来监视这些更改,则无需调用takeRecords方法。调用此方法会清除挂起的相交状态列表,因此不会运行回调。

我们在创建observe对象后,它有一个回调方法,这个回调方法会在创建的时候调用一次(当有监视的对象时),然后当满足一些条件后,会再触发:这个条件就是当监视的节点对象进入显示窗口时和离开显示窗口时。

但是我们怎样知道是哪一个节点对象进入窗口触发的呢?回调函数有个参数,这个参数返回的就是observe对象所监视的所有对象。且当被监视的对象进入窗口后,这个对象的isIntersecting就会变为true,我们还可以通过这个对象的target属性拿到这个对象是监视的哪一个节点。

   let observe = new IntersectionObserver((arr) => {
        arr.forEach(obj=>{
            let target = obj.target;
            if(obj.isIntersecting) {
                //进行操作
            }
        })
    });
    observe.observe(img);
    observe.observe(body);

这样我们就可以基于此进行图片的懒加载或者页面的触底加载啦!

   let observe = new IntersectionObserver((arr) => {
        arr.forEach(obj=>{
            let target = obj.target;
            if(obj.isIntersecting) {
                let src = img.getAttribute('data-src');
                img.setAttribute('src', src);
                observe.unobserve(target);
            }
        })
    });
    let imgs = document.querySelectorAll('img');
    imgs.forEach(img=>{
        observe.observe(img);
    })

这个好处就是当图片加载完成后,就取消对图片的监视,然后当所有的都加载完成后,就不会再触发这个事件,从而节约资源。

而触底加载原理就是当页面的<footer>进入可视区时,就发送ajax请求,从而达到返回的数据再加载数据。

预加载

但是我们发现这种有时候效果不太好,因为要当内容已经进入可视化区域后才进行加载,这样就会有一个停顿进行内容的加载。所以我们可以通过预加载进行优化,就是当内容快要进入可视化区域时就加载!

传统的方式

使用传统的方式进行预加载,也就是让img.getBoundingClientRect().top-window.innerHeight > 某个值 的时候就进行加载。这个值可以是50,100。

IntersectionObserver

但是我们说过传统的方式消耗性能,那么用IntersectionObserver如何进行预加载呢?我们可以在初始化observe实例时传递第二个参数:options配置项,将 rootMargin 的值修改一下(默认是0)。

rootMargin

rootMargin和css的margin差不多,就是给这个属性添加一个外边距,不过rootMargin是给root添加,root默认是浏览器窗口,当然我们可以在配置项里改变它。

我们给浏览器窗口添加外边距后,我们的浏览器窗口就变大了,这时observe的回调函数的触发就不再是监视的节点对象进入可视区域了,而是进入margin值时就会触发了。

所以我们这样就可以就行预加载:

   let observe = new IntersectionObserver((arr) => {
        arr.forEach(obj=>{
            let target = obj.target;
            if(obj.isIntersecting) {
                //进行业务处理,什么发送ajax请求、获取图片等等...
            }
        })
    },{
        rootMargin: "0px 0px 100px 0px"
    });

这样在监视的节点距离可视区域100px时,就会进行图片的加载了。