苏儿 發表於 2019-10-14 00:07:00

【JavaScript】前端性能优化-图片懒加载(附源码)

<p><span style="font-size: 14pt; background-color: rgba(255, 255, 255, 1)"><strong>一、效果图如下</strong></span></p>
<p><img src="https://img2018.cnblogs.com/blog/1254758/201910/1254758-20191013221352874-1049570631.gif"></p>
<p>&nbsp;</p>
<p>上面的效果图,效果需求如下</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 1、还没加载图片的时候,默认显示加载图片背景图</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 2、刚开始进入页面,自动加载第一屏幕的图片</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 3、下拉界面,当一张图片容器完全显露出屏幕,即刻加载图片,替换背景图</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 4、加载图片的时候,有渐进显示图片效果</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="background-color: rgba(255, 255, 255, 1)"><strong><span style="font-size: 14pt">二、难点</span></strong></span></p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 1)如何Ajax请求数据</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 2)如何动态将json数据绑定到html中。</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 3)如何通过对图片的定位计算,触发图片懒加载机制</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 4)加分项,显示图片时有渐现的过渡动画</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="background-color: rgba(255, 255, 255, 1)"><strong><span style="font-size: 14pt">三、前期知识点</span></strong></span></p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 1)Ajax相关知识,XMLHttpRequest对象,所有现代的浏览器都支持此对象。</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 2)innerHTML,数据绑定使用字符串拼接的方式</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 3)HTML DOM getAttribute() 方法,返回自定属性名的属性值(主要是用于返回自定义属性的属性值)</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; 4)图片的 onload事件,当图片的src属性的属性值为正确(即能成功加载图片),才能触发图片的onload事件</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="background-color: rgba(255, 255, 255, 1)"><strong><span style="font-size: 14pt">四、难点逐一攻破</span></strong></span></p>
<p>&nbsp; &nbsp; &nbsp;<strong> &nbsp; </strong><span style="font-size: 18px"><strong>1)</strong>如何Ajax请求数据</span></p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; <span style="font-size: 18px">分四步走</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 1)首先创建一个Ajax对象
var xhr = new XMLHttpRequest;
// 2)打开我们需要请求的数据的那个文件地址
// URL地址后面加随机数目的:清除每一次请求数据时候(get请求)产生的缓存
// 因为每次访问的地址不一样,样浏览器就不会尝试缓存来自服务器的响应,读取本地缓存的数据。
xhr.open('get', 'json/newsList.txt?' + Math.random(), false); // false代表同步
// 3)监听请求的状态
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 &amp;&amp; /^2\d{2}$/.test(xhr.status)) {
      var val = xhr.responseText;
      jsonData = utils.jsonParse(val);
    }
}
// 4)发送请求
xhr.send(null);</pre>
</div>
<p>&nbsp;</p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;</strong><span style="font-size: 18px"><strong>2)</strong>如何动态将json数据绑定到html中。</span></p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;字符串拼接的方式(数据绑定中最常用的方式),</strong>即通过使用<strong>innerHTML</strong>,对页面元素进行字符串拼接,再重新渲染到页面中</p>
<div class="cnblogs_Highlighter">
<pre class="brush:html;gutter:true;">var str = "";
if (jsonData) {
    for (var i = 0, len = jsonData.length; i &lt; len; i++) {
      var curData = jsonData;
      str += '&lt;li&gt;';
      str += '&lt;div&gt;&lt;img src="" trueImg="' + curData["img"] + '"&gt;&lt;/div&gt;';
      str += '&lt;div&gt;&lt;h2&gt;' + curData["title"] + '&lt;/h2&gt;';
      str += '&lt;p&gt;' + curData["desc"] + '&lt;/p&gt;';
      str += '&lt;/div&gt;';
      str += '&lt;/li&gt;&lt;/div&gt;';
    }
    news.innerHTML += str;
}&nbsp;&nbsp;<strong>&nbsp;&nbsp;</strong></pre>
</div>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;优势</strong>:数据绑定最常用的方式,因为浏览器只需要渲染一次(所有模板引擎数据绑定的原理就是字符串拼接,vue、angular、jade、kTemplate.js等等)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;事先把内容拼接好,最后统一添加到页面中,只引发一次回流</p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;弊端</strong>:我们把新凭借的字符串添加到#ul1中,原有的三个li的鼠标滑过效果都消失了(原来标签绑定的事件都消失了)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;原来,oUl.innerHTML的作用是把原来的标签以字符串的方式取出,原来作为标签的时候,对应事件绑定的东西已经没有了,然后进行字符串拼接,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;但是,拼接完成之后,还是字符串!最后再把字符串统一添加到页面中,浏览器还需要把字符串渲染成为对应的标签</p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;<span style="font-size: 18px">&nbsp;</span></strong><span style="font-size: 18px"><strong>3)</strong>如何通过对图片的定位计算,触发图片懒加载机制(</span><span style="font-size: 18px"><span style="color: rgba(255, 0, 0, 1)">最</span></span><span style="font-size: 18px; color: rgba(255, 0, 0, 1)">关键点</span><span style="font-size: 18px">)</span></p>
<p><img src="https://img2018.cnblogs.com/blog/1254758/201910/1254758-20191014000925803-705942102.png"></p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;</strong>思路:</p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span style="font-size: 16px"><span style="color: rgba(255, 0, 0, 1)">&nbsp;</span><span style="color: rgba(0, 0, 0, 1)">A:代表图片距离屏幕顶部的距离&nbsp;</span></span></strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">//这里使用了utils工具类中的offset方法,具体实现看下面源码
var A = utils.offset(curImgPar).offsetTop + curImgPar.offsetHeight; </pre>
</div>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<span style="color: rgba(0, 0, 0, 1); font-size: 16px">B:代表一屏幕距离+滚动条滚动距离</span></strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">//这里使用了utils工具类中的win方法,具体实现看下面源码
var B = utils.win("clientHeight") + utils.win("scrollTop");</pre>
</div>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</strong>当<span style="color: rgba(0, 0, 0, 1); font-size: 14pt"><strong>A &lt; B</strong></span>的时候,此时懒加载的默认图片<span style="color: rgba(0, 0, 0, 1)"><strong>才能完整显示出来</strong></span>,这个时候就需要触发图片懒加载</p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;</strong>&nbsp;<span style="font-size: 18px"><strong>4)</strong>加载图片的时候,有渐进显示图片效果</span></p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;</strong>思路,利用window.setInterval 方法,通过对当前图片的透明度属性(curImg.style.opacity) 从透明0开始到透明度1,变化总时间为500ms即可</p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">// -&gt;实现渐现效果
function fadeIn(curImg) {
    var duration = 500, // 总时间
    interval = 10, //10ms走一次
    target = 1; //总距离是1
    var step = (target / duration) * interval; //每一步的步长
    var timer = window.setInterval(function () {
      var curOp =utils.getCss2SS(curImg, "opacity");
      if (curOp &gt;= 1) {
            curImg.style.opacity = 1;
            window.clearInterval(timer);
            return
      }
      curOp += step;
      curImg.style.opacity = curOp;
    }, interval);
}
</pre>
</div>
<p>&nbsp;</p>
<p> </p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="background-color: rgba(255, 255, 255, 1)"><strong><span style="font-size: 14pt">五、完整代码</span></strong></span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)"><span style="font-size: 14pt">&nbsp; &nbsp; &nbsp; 实现懒加载只需要下面3个文件即可,需要请自行拷贝</span></span></p>
<p><strong>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;<span style="font-size: 18px">&nbsp;</span></strong></strong><span style="font-size: 18px">1)main.html</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:html;gutter:true;">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;!--做移动端响应式布局页面,都需要加下面的meta--&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt; &lt;!--meta:vp+tap一键生成--&gt;
    &lt;title&gt;多张图片的延迟加载&lt;/title&gt;
    &lt;style type="text/css"&gt;
      * {
            margin: 0;
            padding: 0;
            font-family: "Microsoft Sans Serif";
            font-size: 14px;
      }
      ul, li {
            list-style: none;
      }
      img {
            display: block;
            border: none;
      }
      .news {
            padding: 10px;
      }
      .news li {
            position: relative;
            height: 60px;
            padding: 10px 0;
            border-bottom: 1px solid #eee;
      }
      .news li &gt; div:first-child {   /*意思是,li下面的子div,中的第一个*/
            position: absolute;
            top: 10px;
            left: 0;
            width: 75px;
            height: 60px;
            background: url("./img/loading.PNG") no-repeat center center #e1e1e1;
            background-size: 100% 100%;
      }
      /*移动端布局,最外层容器是不设置宽高的*/

      .news li &gt; div:first-child img {
            display: none;
            width: 100%;
            height: 100%;
            opacity: 0;/*这里设置为0的目的是,实现渐进的效果,后面的fadeIn函数,作用就是让图片透明都从0变成1*/
      }

      .news li &gt; div:nth-child(2) {
            height: 60px;
            margin-left: 80px;
      }
      .news li &gt; div:nth-child(2) h2 {
            height: 20px;
            line-height: 20px;
            /*实现文字超出一行自动裁切*/
            overflow: hidden;
            text-overflow: ellipsis; /*超出部分省略号显示*/
            white-space: nowrap; /*强制不换行*/
      }
      .news li &gt; div:nth-child(2) p {
            line-height: 20px;
            font-size: 12px;
            color: #616161;
      }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;ul id="news" class="news"&gt;
      &lt;!--&lt;li&gt;--&gt;
            &lt;!--&lt;div&gt;--&gt;
                &lt;!--&lt;img src="./img/new1.PNG" alt=""&gt;--&gt;
            &lt;!--&lt;/div&gt;--&gt;
            &lt;!--&lt;div&gt;--&gt;
                &lt;!--&lt;h2&gt;香港四大家族往事,香港四大家族往事,香港四大家族往事&lt;/h2&gt;--&gt;
                &lt;!--&lt;p&gt;香港四大家族往事:李嘉诚为郑裕彤扶灵香港四大家族往事:李嘉诚为郑裕彤扶灵&lt;/p&gt;--&gt;
            &lt;!--&lt;/div&gt;--&gt;
      &lt;!--&lt;/li&gt;--&gt;
    &lt;/ul&gt;




&lt;script type="text/javascript" src="./tool/utils.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
    var news = document.getElementById("news"),
      imgList = news.getElementsByTagName("img");

    // 1、获取需要绑定的数据(通过Ajax)
    var jsonData = null;
    ~function () {
      // 1)首先创建一个Ajax对象
      var xhr = new XMLHttpRequest;
      // 2)打开我们需要请求的数据的那个文件地址
      // URL地址后面加随机数目的:清除每一次请求数据时候(get请求)产生的缓存
      // 因为每次访问的地址不一样,样浏览器就不会尝试缓存来自服务器的响应,读取本地缓存的数据。
      xhr.open('get', 'json/newsList.txt?' + Math.random(), false); // false代表同步
      // 3)监听请求的状态
      xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 &amp;&amp; /^2\d{2}$/.test(xhr.status)) {
                var val = xhr.responseText;
                jsonData = utils.jsonParse(val);
            }
      }
      // 4)发送请求
      xhr.send(null);
    }();
    console.log(jsonData);

    // 2、数据绑定(使用字符串拼接的方式)
    ~function () {
      var str = "";
      if (jsonData) {
            for (var i = 0, len = jsonData.length; i &lt; len; i++) {
                var curData = jsonData;
                str += '&lt;li&gt;';
                str += '&lt;div&gt;&lt;img src="" trueImg="' + curData["img"] + '"&gt;&lt;/div&gt;';
                str += '&lt;div&gt;&lt;h2&gt;' + curData["title"] + '&lt;/h2&gt;';
                str += '&lt;p&gt;' + curData["desc"] + '&lt;/p&gt;';
                str += '&lt;/div&gt;';
                str += '&lt;/li&gt;&lt;/div&gt;';
            }
            news.innerHTML += str;
      }
    }();

    // 3、图片延迟加载
    // -&gt;首先实现单张图片的延时加载
    function lazyImg(curImg) {
      var oImg = new Image;
      oImg.src = curImg.getAttribute("trueImg");
      oImg.onload = function() {
            curImg.src = this.src;
            curImg.style.display = "block";
            fadeIn(curImg);
            oImg = null;
      }
      curImg.isLoad = true;
    }

    // -&gt; 循环处理每一张图片
    function handleAllImg() {
      for (var i = 0, len = imgList.length; i &lt; len; i++) {
            var curImg = imgList;
            if (curImg.isLoad) { // 当前图片处理过的话,就不需重新进行处理
                continue;
            }

            // -&gt;只有当A小于B的时候再进行处理
//          var A = utils.offset(curImg).top + curImg.offsetHeight; // 这里A不能这么计算,因为此时图片是隐藏的,没有图片,他的offsetHeight当让也是为0
                                                                      // 如果我要的到图片的A值,我们可以通过拿到他父节点的容器就行了,哈哈
            var curImgPar = curImg.parentNode,
                A = utils.offset(curImgPar).offsetTop + curImgPar.offsetHeight,
                B = utils.win("clientHeight") + utils.win("scrollTop");
            if (A &lt; B) {
                lazyImg(curImg);
            }
      }
    }

    // -&gt;实现渐现效果
    function fadeIn(curImg) {
      var duration = 500, // 总时间
            interval = 10, //10ms走一次
            target = 1; //总距离是1
      var step = (target / duration) * interval; //每一步的步长
      var timer = window.setInterval(function () {
            var curOp =utils.getCss2SS(curImg, "opacity");
            if (curOp &gt;= 1) {
                  curImg.style.opacity = 1;
                  window.clearInterval(timer);
                  return
            }
            curOp += step;
            curImg.style.opacity = curOp;
      }, interval);
    }

    // 4、开始的时候(过500ms)加载1屏幕的图片,当滚动条滚动的时候,加载其他图片
    window.setTimeout(handleAllImg, 500);
    window.onscroll = handleAllImg;
   
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
</div>
<p>  </p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;<strong>&nbsp;&nbsp;</strong><span style="font-size: 18px">2)utils.js</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">// 为了与全局变量冲突,我们使用单例模式
var utils = {
// jsonParse: 把JSON格式的字符串转化为JSON格式的对象
jsonParse: function (str) {
      var val = null;
       try {
          val = JSON.parse(str);
      } catch (e) {
          val = eval('(' + str + ')');
      }
      return val;
},

getCss2SS : function(curEle, attr) {
      var val = null, reg = null;
      if ('getComputedStyle' in window) {
          val = window.getComputedStyle(curEle, null);
      } else {
          if (attr === 'opacity') {
            val = curEle.currentStyle; // -&gt;返回 alpha(opacity=10)
            reg = /^alpha\(opacity=(\d+(?:\.\d+)?)\)$/i;//获取10这个数字
            val = reg.test(val)?reg.exec(val)/100:1// 超厉害,test与exec一起使用!!!
          }
          val = curEle.currentStyle;
      }
      reg = /^-?\d+(\.\d+)?(px|pt|rem|em)?$/i; //匹配的情况:纯数值或者带单位的数值
      return reg.test(val) ? parseFloat(val) : val;
},

offset : function(curEle) {
      var totalLeft = null,
          totalTop = null,
          par = curEle.offsetParent;
      // 首先把自己本身的进行累加
      totalLeft += curEle.offsetLeft;
      totalTop += curEle.offsetTop;

      while (par) {
          if (navigator.userAgent.indexOf("MSIE 8.0") === -1) {
            // 累加父级参照物边框
            totalTop += par.clientTop;
            totalLeft += par.clientLeft;
          }
          // 累加父级参照物本身的偏移
          totalTop += par.offsetTop;
          totalLeft += par.offsetLeft;
          par = par.offsetParent;
      }
      console.log('offsetTop: ' + totalTop + ', offsetLeft: ' + totalLeft);
      var result = {};
      result.offsetTop = totalTop;
      result.offsetLeft = totalLeft;
      return result;
},

win : function(attr, value) {
      if (value === undefined) {
          return document.documentElement || document.body;
      }
      document.documentElement = value;
      document.body = value;
}
};
</pre>
</div>
<p>&nbsp;</p>
<p>&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;<span style="font-size: 18px">&nbsp;3、json文件</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">[{"img":"./img/new1.PNG", "title": "1网络强国战略与“十三五”十四大战略", "desc": "1互联网是二十世纪人类最大的发明,互联网是二十世纪人类最大的发明"},
{"img":"./img/new2.PNG", "title": "2网络强国战略与“十三五”十四大战略", "desc": "2互联网是二十世纪人类最大的发明,互联网是二十世纪人类最大的发明"},
{"img":"./img/new3.PNG", "title": "3网络强国战略与“十三五”十四大战略", "desc": "3互联网是二十世纪人类最大的发明,互联网是二十世纪人类最大的发明"}
]
</pre>
</div>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    不安逸,不浮躁,牛B就是一个学习累积的过程<br><br>
来源:https://www.cnblogs.com/pengshengguang/p/11669176.html
頁: [1]
查看完整版本: 【JavaScript】前端性能优化-图片懒加载(附源码)