基于纯JS实现多张图片的懒加载Lazy过程解析


Posted in Javascript onOctober 14, 2019

一、效果图如下

基于纯JS实现多张图片的懒加载Lazy过程解析

上面的效果图,效果需求如下

1、还没加载图片的时候,默认显示加载图片背景图

2、刚开始进入页面,自动加载第一屏幕的图片

3、下拉界面,当一张图片容器完全显露出屏幕,即刻加载图片,替换背景图

4、加载图片的时候,有渐进显示图片效果

二、难点

1)如何Ajax请求数据

2)如何动态将json数据绑定到html中。

3)如何通过对图片的定位计算,触发图片懒加载机制

4)加分项,显示图片时有渐现的过渡动画

三、前期知识点

1)Ajax相关知识,XMLHttpRequest对象,所有现代的浏览器都支持此对象。

2)innerHTML,数据绑定使用字符串拼接的方式

3)HTML DOM getAttribute() 方法,返回自定属性名的属性值(主要是用于返回自定义属性的属性值)

4)图片的 onload事件,当图片的src属性的属性值为正确(即能成功加载图片),才能触发图片的onload事件

四、难点逐一攻破

1)如何Ajax请求数据

分四步走

// 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 && /^2\d{2}$/.test(xhr.status)) {
    var val = xhr.responseText;
    jsonData = utils.jsonParse(val);
  }
}
// 4)发送请求
xhr.send(null);

2)如何动态将json数据绑定到html中。

字符串拼接的方式(数据绑定中最常用的方式),即通过使用innerHTML,对页面元素进行字符串拼接,再重新渲染到页面中

var str = "";
if (jsonData) {
  for (var i = 0, len = jsonData.length; i < len; i++) {
    var curData = jsonData[i];
    str += '<li>';
    str += '<div><img src="" trueImg="' + curData["img"] + '"></div>';
    str += '<div><h2>' + curData["title"] + '</h2>';
    str += '<p>' + curData["desc"] + '</p>';
    str += '</div>';
    str += '</li></div>';
  }
  news.innerHTML += str;
} <strong> </strong>

优势:数据绑定最常用的方式,因为浏览器只需要渲染一次(所有模板引擎数据绑定的原理就是字符串拼接,vue、angular、jade、kTemplate.js等等)

事先把内容拼接好,最后统一添加到页面中,只引发一次回流

弊端:我们把新凭借的字符串添加到#ul1中,原有的三个li的鼠标滑过效果都消失了(原来标签绑定的事件都消失了)
原来,oUl.innerHTML的作用是把原来的标签以字符串的方式取出,原来作为标签的时候,对应事件绑定的东西已经没有了,然后进行字符串拼接,

但是,拼接完成之后,还是字符串!最后再把字符串统一添加到页面中,浏览器还需要把字符串渲染成为对应的标签

3)如何通过对图片的定位计算,触发图片懒加载机制(最关键点)

基于纯JS实现多张图片的懒加载Lazy过程解析

思路:

A:代表图片距离屏幕顶部的距离

//这里使用了utils工具类中的offset方法,具体实现看下面源码
var A = utils.offset(curImgPar).offsetTop + curImgPar.offsetHeight;

B:代表一屏幕距离+滚动条滚动距离

//这里使用了utils工具类中的win方法,具体实现看下面源码
var B = utils.win("clientHeight") + utils.win("scrollTop");

当A < B的时候,此时懒加载的默认图片才能完整显示出来,这个时候就需要触发图片懒加载

4)加载图片的时候,有渐进显示图片效果

思路,利用window.setInterval 方法,通过对当前图片的透明度属性(curImg.style.opacity) 从透明0开始到透明度1,变化总时间为500ms即可

// ->实现渐现效果
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 >= 1) {
      curImg.style.opacity = 1;
      window.clearInterval(timer);
      return
    }
    curOp += step;
    curImg.style.opacity = curOp;
  }, interval);
}

五、完整代码

1)main.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <!--做移动端响应式布局页面,都需要加下面的meta-->
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!--meta:vp+tap一键生成-->
  <title>多张图片的延迟加载</title>
  <style type="text/css">
    * {
      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 > 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 > div:first-child img {
      display: none;
      width: 100%;
      height: 100%;
      opacity: 0; /*这里设置为0的目的是,实现渐进的效果,后面的fadeIn函数,作用就是让图片透明都从0变成1*/
    }
 
    .news li > div:nth-child(2) {
      height: 60px;
      margin-left: 80px;
    }
    .news li > div:nth-child(2) h2 {
      height: 20px;
      line-height: 20px;
      /*实现文字超出一行自动裁切*/
      overflow: hidden;
      text-overflow: ellipsis; /*超出部分省略号显示*/
      white-space: nowrap; /*强制不换行*/
    }
    .news li > div:nth-child(2) p {
      line-height: 20px;
      font-size: 12px;
      color: #616161;
    }
  </style>
</head>
<body>
  <ul id="news" class="news">
    <!--<li>-->
      <!--<div>-->
        <!--<img src="./img/new1.PNG" alt="">-->
      <!--</div>-->
      <!--<div>-->
        <!--<h2>香港四大家族往事,香港四大家族往事,香港四大家族往事</h2>-->
        <!--<p>香港四大家族往事:李嘉诚为郑裕彤扶灵香港四大家族往事:李嘉诚为郑裕彤扶灵</p>-->
      <!--</div>-->
    <!--</li>-->
  </ul>
 
 
 
 
<script type="text/javascript" src="./tool/utils.js"></script>
<script type="text/javascript">
  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 && /^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 < len; i++) {
        var curData = jsonData[i];
        str += '<li>';
        str += '<div><img src="" trueImg="' + curData["img"] + '"></div>';
        str += '<div><h2>' + curData["title"] + '</h2>';
        str += '<p>' + curData["desc"] + '</p>';
        str += '</div>';
        str += '</li></div>';
      }
      news.innerHTML += str;
    }
  }();
 
  // 3、图片延迟加载
  // ->首先实现单张图片的延时加载
  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;
  }
 
  // -> 循环处理每一张图片
  function handleAllImg() {
    for (var i = 0, len = imgList.length; i < len; i++) {
      var curImg = imgList[i];
      if (curImg.isLoad) { // 当前图片处理过的话,就不需重新进行处理
        continue;
      }
 
      // ->只有当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 < B) {
        lazyImg(curImg);
      }
    }
  }
 
  // ->实现渐现效果
  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 >= 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;
   
</script>
</body>
</html>

2)utils.js

// 为了与全局变量冲突,我们使用单例模式
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)[attr];
   } else {
     if (attr === 'opacity') {
       val = curEle.currentStyle[attr]; // ->返回 alpha(opacity=10)
       reg = /^alpha\(opacity=(\d+(?:\.\d+)?)\)$/i; // 获取10这个数字
       val = reg.test(val)?reg.exec(val)[1]/100:1 // 超厉害,test与exec一起使用!!!
     }
     val = curEle.currentStyle[attr];
   }
   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[attr] || document.body[attr];
   }
   document.documentElement[attr] = value;
   document.body[attr] = value;
 }
};

3、json文件

[{"img":"./img/new1.PNG", "title": "1网络强国战略与“十三五”十四大战略", "desc": "1互联网是二十世纪人类最大的发明,互联网是二十世纪人类最大的发明"},
 {"img":"./img/new2.PNG", "title": "2网络强国战略与“十三五”十四大战略", "desc": "2互联网是二十世纪人类最大的发明,互联网是二十世纪人类最大的发明"},
 {"img":"./img/new3.PNG", "title": "3网络强国战略与“十三五”十四大战略", "desc": "3互联网是二十世纪人类最大的发明,互联网是二十世纪人类最大的发明"}
]

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS Timing
Apr 21 Javascript
在网页中使用document.write时遭遇的奇怪问题
Aug 24 Javascript
JavaScript中的关键字&quot;VAR&quot;使用详解 分享
Jul 31 Javascript
js中top的作用深入剖析
Mar 04 Javascript
屏蔽相应键盘按钮操作
Mar 10 Javascript
充分发挥Node.js程序性能的一些方法介绍
Jun 23 Javascript
json格式数据的添加,删除及排序方法
Jan 21 Javascript
深入浅析JavaScript中的arguments对象(强力推荐)
Jun 03 Javascript
微信小程序 开发之全局配置
May 05 Javascript
vue.js实现备忘录功能的方法
Jul 10 Javascript
JS获取填报扩展单元格控件的值的解决办法
Jul 14 Javascript
vue实现点击按钮切换背景颜色的示例代码
Jun 23 Javascript
VUE+node(express)实现前后端分离
Oct 13 #Javascript
javascript sort()对数组中的元素进行排序详解
Oct 13 #Javascript
javaScript把其它类型转换为Number类型
Oct 13 #Javascript
js 实现watch监听数据变化的代码
Oct 13 #Javascript
15 分钟掌握vue-next响应式原理
Oct 13 #Javascript
Vue3.x源码调试的实现方法
Oct 13 #Javascript
使用webpack将ES6转化ES5的实现方法
Oct 13 #Javascript
You might like
跟我学小偷程序之成功偷取首页(第三天)
2006/10/09 PHP
简单谈谈 php 文件锁
2017/02/19 PHP
php实现留言板功能
2017/03/05 PHP
用javascript动态调整iframe高度的方法
2007/03/06 Javascript
统一接口:为FireFox添加IE的方法和属性的js代码
2007/03/25 Javascript
javascript 验证日期的函数
2010/03/18 Javascript
jquery中ajax学习笔记一
2011/10/16 Javascript
jQuery简单实现banner图片切换
2014/01/02 Javascript
AMD异步模块定义介绍和Require.js中使用jQuery及jQuery插件的方法
2014/06/06 Javascript
深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例
2014/08/04 Javascript
微信小程序 富文本转文本实例详解
2016/10/24 Javascript
Bootstrap基本组件学习笔记之缩略图(13)
2016/12/08 Javascript
jQuery+ajax实现修改密码验证功能实例详解
2017/07/06 jQuery
Vue中this.$router.push参数获取方法
2018/02/27 Javascript
angular 实现的输入框数字千分位及保留几位小数点功能示例
2018/06/19 Javascript
Vue 使用计时器实现跑马灯效果的实例代码
2019/07/11 Javascript
vue+elementUi图片上传组件使用详解
2019/08/20 Javascript
js实现GIF图片的分解和合成
2019/10/24 Javascript
[02:03]《现实生活中的DOTA2》—林书豪&DOTA2职业选手出演短片
2015/08/18 DOTA
[54:29]2018DOTA2亚洲邀请赛 4.7 淘汰赛 VP vs LGD 第二场
2018/04/09 DOTA
Python重新引入被覆盖的自带function
2014/07/16 Python
讲解Python中的递归函数
2015/04/27 Python
利用python代码写的12306订票代码
2015/12/20 Python
Python网站验证码识别
2016/01/25 Python
深入理解Python中装饰器的用法
2016/06/28 Python
django请求返回不同的类型图片json,xml,html的实例
2018/05/22 Python
Django logging配置及使用详解
2019/07/23 Python
详解Python中的文件操作
2021/01/14 Python
德国婴儿推车和儿童安全座椅商店:BABYSHOP
2016/09/01 全球购物
实习教师自我鉴定
2013/12/12 职场文书
农贸市场管理制度
2014/01/31 职场文书
岗位职责说明书模板
2014/07/30 职场文书
企业领导班子四风对照检查材料
2014/09/27 职场文书
2015年留守儿童工作总结
2015/05/22 职场文书
python中pandas.read_csv()函数的深入讲解
2021/03/29 Python
基于Python和openCV实现图像的全景拼接详细步骤
2021/10/05 Python