美文网首页
现代化懒加载的方式

现代化懒加载的方式

作者: 流动码文 | 来源:发表于2018-02-06 10:29 被阅读18次

    现代化懒加载的方式

    为什么需要懒加载

    通常用户打开网页时,整个网页的内容将被下载并且呈现在一个页面中,虽然允许浏览器缓存页面,但是不能保证用户查看所有下载的的内容,例如一个照片墙应用,可能用户仅仅查看第一个图片之后离开,结果就是白白浪费了内存和带宽。因此我们需要当用户需要访问页面的一部分时才去加载内容,而不是一看是就去加载全部内容。

    如何实现懒加载

    当有人向网页(图像,视频)等资源,资源引用一个小的占位符,当用户浏览网页,实际的资源被浏览器缓存,并且当资源在屏幕上可见时替换占位符,例如,如果用户加载网页并立即离开网页,则除了网页的顶部之外没有任何内容被加载。

    <figure style="display: block; margin: 2.7rem auto; text-align: center;"> image

    <figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>

    </figure>

    懒加载具体实现

    以加载图片为例子,我们需要将img标签中设置一个data-src属性,它指向的是实际上我们需要加载的图像,而imgsrc指向一张默认的图片,如果为空的话也会向服务器发送请求。

    <img src="default.jpg" data-src="www.example.com/1.jpg">
    
    

    之后当用户访问的可视区域的img元素时,将src得值替换为data-src指向的实际资源加载的图像

    具体代码

    const lazy = (el) => {
     let scrTop = getTop();
     let windowHeight = document.documentElement.clientHeight;
     function getTop(){
      return document.documentElement.scrollTop || document.body.scrollTop; 
     }
     function getOffset(node){
      return node.getBoundingClientRect().top + scrTop;
     }
     function inView(node){
      // 设立阈值
     const threshold = 0;
     const viewTop = scrTop;
     const viewBot = viewTop + windowHeight;
    
     const nodeTop = getOffset(node);
     const nodeBot = nodeTop + node.offsetHeight;
    
     const offset = (threshold / 100) * windowHeight;
     console.log((nodeBot >= viewTop - offset), (nodeTop <= viewBot + offset))
        return (nodeBot >= viewTop - offset) && (nodeTop <= viewBot + offset)
     }
     function check(node){
       let el = document.querySelector(node);
       let images = [...el.querySelectorAll('img')];
       images.forEach(img => {
        if(inView(img)){
         img.src = img.dataset.src;
        }
       })
     }
     check(el);
    }
    
    window.onscroll = function(){
     lazy('.foo');
    }
    
    

    现代化懒加载实现方法

    通过上面例子的实现,我们要实现懒加载都需要去监听scroll事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算scrollTop,offsetHeight等属性,有没有简单的不需要计算这些属性的方式呢,答案是有的---IntersectionObserver

    根据MDN:

    IntersectionObserver API为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)处于交叉状态的方式。祖先元素与视窗(viewport)被称为根(root)。

    简单来说就是观察一个元素和另一个元素是否重叠。

    IntersectionObserver初始化的过程中提供了三个主要元素的配置:

    • root: 这是用于观察的根元素。他定义了可观察元素的基本捕获框架,默认情况下,root指向的是浏览器的视口,但实际上可以是任意的DOM元素,要注意的是:root在这种情况下,要观察元素的必选要在root代表的Dom元素内部
    image.png
    <figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>
    
    </figure>
    
    • rootMargin: 计算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的判定范围从而满足计算需要。值得选项与marginCSS类似,比如rootMargin: '50px 20px 10px 40px'(top, right, bottom, left)
    image.png
    <figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>
    
    </figure>
    
    • threshold: 一个包含阈值的list, 升序排列, list中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会生成一个通知(Notification)。如果构造器未传入值, 则默认值为0.
    image.png
    <figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 2.7rem; color: rgb(144, 144, 144);"></figcaption>
    
    </figure>
    
    为了告诉我们`intersectionObserver`我们想要得配置,我们只需要将我们得`config`对象和我们的回调函数一起传递到Observer构造函数中
    
    const config = {
        root: null,
        rootMargin: '0px',
        threshold: 0.5
    }
    let observer = new IntersectionObserver(fucntion(entries){
        // ...
    }, config)
    
    

    现在我们需要去给IntersectionObserver实际观察的元素

    const img = document.querySelector('image');
    observer.observe(img);
    
    

    关于这个实际观察的元素需要注意几点

    • 首先他应该位于root代表的DOM元素中
    • IntersectionObserver一次只能接受一个观察元素,不支持批量观察。这意味着如果你需要观察几个元素(比如说一个页面上的几个图像),你必须遍历所有元素并分别观察它们中的每一个
    const images = document.querySelecttorAll('img');
    images.forEach(image => {
        observer.observe(image)
    })
    
    
    • 当使用Observer加载页面时,您可能会注意到,IntersectionObserver所有观察到的元素的回调已经被触发了。我们可以通过回调函数来解决这个问题

    IntersectionObserver回调函数

    new IntersectionObserver(function(entries, self))
    
    

    entries我们得到我们的回调函数作为Array是特殊类型的:IntersectionObserverEntry首先IntersectionObserverEntry含有三个不同的矩形的信息

    • rootBounds: '捕捉框架(root + rootMargin)'的矩形

    • boundClientRect: 观察元素本身的矩形

    • intersectionRect: 捕捉框架和观察元素相交的矩形

      image.png

      此外,IntersectionObserverEntry还提供了isIntersecting,这是一个方便的属性,返回观察元素是否与捕获框架相交, 另外,IntersectionObserverEntry提供了利于计算的遍历属性intersctionRatio:返回intersectionRect 与 boundingClientRect 的比例值.

    image.png
    target则返回要观察的元素 好了

    简单介绍完,让我们回到正题,用这个IntersectionObserver来实现代化的懒加载方式吧

    const images = document.querySelectorAll('[data-src]')
    const config = {
        rootMargin: '0px',
        threshold: 0
    };
    let observer = new IntersectionObserver((entries, self)=>{
        entries.forEach(entry => {
            if(entry.isIntersecting){
             // 加载图像
             preloadImage(entry.target);
             // 解除观察
               self.unobserve(entry.target)
            }
        })
    }, config)
    
    images.forEach(image => {
      observer.observe(image);
    });
    
    function preloadImage(img) {
      const src = img.dataset.src
      if (!src) { return; }
      img.src = src;
    }
    
    

    相比于之前懒加载的方式是不是更加简洁,而且只有当观察元素和捕捉框架交叉或重叠时,才会触发回掉函数(加载页面时也会触发回调函数,不过我们可以用isIntersecting来进行判断是否和观察元素相交)

    延迟加载的好处

    • 延迟加载在优化内容加载和简化最终用户体验之间达成了平衡。
    • 用户可以更快地加载到内容,因为用户第一次打开网站时只需要加载一部分内容。
    • 网站看到更高的用户保留,因为不断向用户提供内容,减少了用户离开网站的机会。
    • 网站看到较低的资源成本,因为内容只在用户需要时才加载,而不是一次完成。

    该方法目前有兼容性问题, github上有polyfill
    https://github.com/w3c/IntersectionObserver/tree/master/polyfill

    参考:

    MDN

    Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver

    What is Lazy Loading?

    相关文章

      网友评论

          本文标题:现代化懒加载的方式

          本文链接:https://www.haomeiwen.com/subject/rnawzxtx.html