zoukankan      html  css  js  c++  java
  • JS实现图片懒加载插件

    一、前言

     我在前几篇博客的记录中,有说自己在做一个图片懒加载的功能,然后巴拉巴拉的遇到哪些问题,结果做完了也没对懒加载这个功能做一些记录,所以这篇文章主要针对我所实现的思路,以及代码做个记录,实现不佳之处还望见谅和指出。

    二、实现原理与相关问题

    1.做成一个组件还是service?

    公司框架是angular,类似于图片懒加载这类较通用的功能,肯定得保证复用性与可拓展性,同事建议做成组件,哪张图片需要懒加载给这个图片添加组件名;我心想,那repeat出来一百张图,那岂不是瞬间瞬间100个组件同时运行,内存爆炸....听起来不是很优化的感觉。想了下,还是做成service,哪里需要注入我的service,调用我提前提供好的方法即可。

    基本功能流程图如上,提前为需要懒加载的图片统一添加lazyImg-src="真实图片地址">属性,页面加载完成获取所有有lazyImg-src属性的dom,获取dom操作只会执行一次,我不想每次滚动都会反复获取dom,这样太友好。

     页面加载完成会自调一次懒加载函数,之后就交给滚动事件触发,当一个dom元素的图片替换完成,就将此dom从数组中删除,懒加载函数执行的前置条件为dom数组不为空,那么当第一次懒加载完后无论用户怎么滚动,核心代码不会再执行。

    2.如何判断图片是否在视图框内?

    这个问题可以说是懒加载的核心问题,弄懂这个,功能已经做了一大半了,我们来看一个图:

     判断一张图是否在视图范围内,需要判断X轴与Y轴的进入,离开的两种情况,加起来就是四个条件,只要同时满足,则元素一定在视图范围内。

    3.怎么使用?

    给需要懒加载的元素添加lazyImg-src属性,里面存放真实的图片地址,为了更好的体验,你可以将图片路径设置为一张loading的图片,在懒加载时再进行替换。

    调用lazyload方法,并传入视图对象,什么意思呢,比如下面给出的demo代码中,位置参照对象是window,视图对象是ul,判断的是每张img。

    当触发滚动事件时,监听的是ul的滚动条,步骤如下,引入JS

    先获取视图对象,例如:

    const viewDom = document.querySelector('.container');

    然后调用方法lazyload(viewDom )即可。

    你也可以不传递视图对象,那么此时视图对象会默认为window,也就是说当触发滚动事件时,监听的是window的滚动条。

     三、实现代码

    效果图,此时的滚动事件监听的是ul的滚动条。随着滚动,netWork中可以看到img在一张张加载。

     

    HTML部分,图片自己准备,懒加载JS记得引入

        <ul class="container">
            <li><img src="img/timg.gif" alt="1" lazyImg-src="img/1.jpg"></li>
            <li><img src="img/timg.gif" alt="2" lazyImg-src="img/2.jpg"></li>
            <li><img src="img/timg.gif" alt="3" lazyImg-src="img/3.jpg"></li>
            <li><img src="img/timg.gif" alt="4" lazyImg-src="img/4.jpg"></li>
            <li><img src="img/timg.gif" alt="5" lazyImg-src="img/5.jpg"></li>
            <li><img src="img/timg.gif" alt="6" lazyImg-src="img/6.jpg"></li>
            <li><img src="img/timg.gif" alt="7" lazyImg-src="img/7.jpg"></li>
            <li><img src="img/timg.gif" alt="8" lazyImg-src="img/8.jpg"></li>
            <li><img src="img/timg.gif" alt="9" lazyImg-src="img/9.jpg"></li>
            <li><img src="img/timg.gif" alt="10" lazyImg-src="img/10.jpg"></li>
        </ul>

    CSS部分:

    body{
        position:relative;
        padding:0;
        margin:0;
        display: table;
    }
    .container{
        list-style: none;
        height: 400px;
         250px;
        overflow: auto;
        margin-top:200px;
    }
    .container>li{
        margin-bottom: 10px;
    }
    .container>li>img{
        background-color: #e4393c;
         200px;
        height: 200px;
    }

    JS部分,别被变量的数量吓到,搞懂原理其实真的不复杂,或者打断点跟着跑一遍。

    //处理图片懒加载
    function lazyload (option) {
      //如果不传递视图对象,则默认视图对象为window
      var viewDom = option || window,
          scroll_top, //滚动条Y轴距离
          scroll_left, //滚动条X轴距离
          viewDomWidth, //视图宽
          viewDomHeight,//视图高
          viewDomLeft, //视图左偏移量
          viewDomTop, //视图上偏移量
          imgWidth, //img宽
          imgHeight, //img高
          imgLeft, //img左偏移量
          imgTop, //img上偏移量
          imgArr = [],
          doc = document.documentElement;
    
      //获取视图元素宽高,左上偏移量,分为window或普通dom两种情况
      if (viewDom === window) {
        viewDomWidth = doc.clientWidth; 
        viewDomHeight = doc.clientHeight;
        viewDomLeft = doc.offsetLeft;
        viewDomTop = doc.offsetTop;
      }else{
        viewDomWidth = viewDom.offsetWidth;
        viewDomHeight = viewDom.offsetHeight;
        viewDomLeft = viewDom.offsetLeft;
        viewDomTop = viewDom.offsetTop;
      };
    
      /**
       * @desc 将nodeList转为数组,方便操作
       * @param {object} data nodelist对象
       */
      function nodeListToArr(data) {
        var arr = [];
        try{
          //ie8及以下不支
          arr = Array.prototype.slice.call(data);
        }catch(e){
          //兼容写法
          var len = data.length;
          for(var i = 0; i < len; i++){
            arr.push(data[i]);
          }
        };
        return arr;
      };
      //取得所有需要懒加载的dom元素
      var imgNode = document.querySelectorAll("[lazyImg-src]");
      imgArr = nodeListToArr(imgNode);
      //替换路径函数,懒加载核心函数
      function replaceUrl () {
        //只有img数组不为空,才会执行替换路径的操作
        if(imgArr.length){
          var i = 0;
          //获取视图元素滚动条滚动距离
          if (viewDom === window) {
            scroll_top = doc.scrollTop;
            scroll_left = doc.scrollLeft;
          }else{
            scroll_top = viewDom.scrollTop;
            scroll_left = viewDom.scrollLeft;
          };
          //遍历需要懒加载的dom节点
          for (; i<imgArr.length; i++) {
            //获取当前元素的宽高
            imgWidth = imgArr[i].offsetWidth;
            imgHeight = imgArr[i].offsetHeight;
            //当前元素随着滚动的变化的偏移量为
            imgLeft = imgArr[i].offsetLeft - scroll_left;
            imgTop = imgArr[i].offsetTop - scroll_top;
            //横向判断是否进入视图元素
            var boundaryLeft = imgLeft + imgWidth - viewDomLeft;
            var boundaryRight = viewDomLeft + viewDomWidth - imgLeft;
            //同理纵向判断是否进入视图元素
            var boundaryTop = imgTop + imgHeight - viewDomTop;
            var boundaryBottom = viewDomTop + viewDomHeight - imgTop;
            if(boundaryLeft>0 && boundaryRight>0 && boundaryTop>0 && boundaryBottom>0) {
              //替换图片路径,添加判断,img替换src,非img替换background
              if(imgArr[i].nodeName === "IMG"){
                imgArr[i].src = imgArr[i].attributes['lazyimg-src'].value;
              }else{
                //不是图片的替换背景图
                // imgArr[i].style.backgroundImage = imgArr[i].attributes['lazyimg-src'].value;
              };
              //操作完一项就删除一项,同时重置i;
              imgArr.splice(i,1);
              i -= 1;
            };
          };
        };
      };
      //自调
      replaceUrl();
      //添加事件监听
      viewDom.addEventListener("scroll",replaceUrl);
    };
    //测试调用,默认绑定window,或传递你需要监听的dom对象,调用就2步
    var container = document.querySelector(".container");
    lazyload(container);

     四、遇到的一些问题

    1.判断元素是否在视图内,就Y轴而言,随着滚动条向下滚动,可以说img元素距离参照物上端会越来越近,也就说这个img上偏移量会越来越小,然后我果断用了JS的offsetTop属性来获取上偏移量,结果踩了个坑,但用JQ的offset().top却没问题,原因是offsetTop获取的是img初始位置的上偏移量,不随滚动条滚动变化,而offset().top是变化的,具体想知道JS offsetTop与offset().top不同以及怎么通过JS获取可变的上偏移量,可以阅读博主这篇文章。

    JQ的offset().top与js的offsetTop区别详解

    2.此实现方法需要一开始获取所有需要懒加载的dom元素,而公司用的angular,angular有自己的生命周期,怎么保证我获取dom的时候,所有angular模板以及指令已完全渲染完成呢?有兴趣可以阅读博主这篇文章:

    angular监听dom渲染完成,判断ng-repeat循环完成

    3.通过querySelector获取的dom其实是一个domList对象,然而我在处理完每个dom后想要删除此项,而domList对象不支持数组方法,怎么把domList转为数组对象,可以看这篇文章。

    JS nodeList转数组,兼容IE低版本

    那么大概记录这么多了,本人本地运行是没问题的,有问题欢迎大家指出。

  • 相关阅读:
    模型绑定功能
    接口返回的内容
    跨平台的ASP.NET Core简介
    NLog如何打印日志(.Net5)
    注意力创造价值;
    Restful接口的介绍
    电脑设置双屏显示(windows)
    Linq多集合连接
    调试时才执行的代码
    mvc4 路由匹配测试
  • 原文地址:https://www.cnblogs.com/echolun/p/10260272.html
Copyright © 2011-2022 走看看