面试官:图片加载优化中,你是如何实现预加载和懒加载的?

一、图片预加载

预加载图片是提高用户体验的一个很好方法。图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度。这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速、无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验。通常情况下,平常见得比较多的是这种写法。

(1)多张图片

通过实例化Image对象 调用onload方法来实现(onload之后)

var oDiv = document.getElementsByClassName('div')[0],
    img = ['https://zza.jpg''https://zzb.jpg'];
    img.forEach(function(elem){
        var oImg = new Image();
        oImg.src = elem;
        oImg.style.width = '100%';
        oImg.onload = function(){
            oDiv.appendChild(oImg);
        }
    })

(2)单张照片

var oDiv = document.getElementsByTagName('div')[0],
var oImg = new Image();
oImg.src = 'https://zzz.jpg';
oImg.style.width = '100%';
oImg.onload = function(){
    oDiv.appendChild(oImg);
}

接下来将归纳一下实现预加载的三种方法。

CSS+JavaScript实现预加载

实现预加载图片有很多方法,包括使用CSS、JavaScript及两者的各种组合。这些技术可根据不同设计场景设计出相应的解决方案,十分高效。单纯使用CSS,可容易、高效地预加载图片,代码如下:

#preload-01 { backgroundurl(http://img.netbian.com/file/2020/1123/small5027584d7ae249306584b186d876b4381606145734.jpg) no-repeat -9999px -9999px; }
#preload-02 { backgroundurl(http://img.netbian.com/file/2020/1123/small5887a5aa6ff9e7b7932e89ef616d24251606131346.jpg) no-repeat -9999px -9999px; }
#preload-03 { backgroundurl(http://img.netbian.com/file/2020/1123/smalledec4b73a9febffb61a0857b7a1221be1606131242.jpg) no-repeat -9999px -9999px; }

将这三个ID选择器应用到(X)HTML元素中,我们便可通过CSS的background属性将图片预加载到屏幕外的背景上。只要这些图片的路径保持不变,当它们在Web页面的其他地方被调用时,浏览器就会在渲染过程中使用预加载(缓存)的图片。简单、高效,不需要任何JavaScript。该方法虽然高效,但仍有改进余地。使用该法加载的图片会同页面的其他内容一起加载,增加了页面的整体加载时间。为了解决这个问题,我们增加了一些JavaScript代码,来推迟预加载的时间,直到页面加载完毕。代码如下:

function preloader({
 if (document.getElementById) {
  document.getElementById("preload-01").style.background = "url(http://http://img.netbian.com/file/2020/1123/small5027584d7ae249306584b186d876b4381606145734.jpg) no-repeat -9999px -9999px";
  document.getElementById("preload-02").style.background = "url(http://img.netbian.com/file/2020/1123/small5887a5aa6ff9e7b7932e89ef616d24251606131346.jpg) no-repeat -9999px -9999px";
  document.getElementById("preload-03").style.background = "url(http://img.netbian.com/file/2020/1123/smalledec4b73a9febffb61a0857b7a1221be1606131242.jpg) no-repeat -9999px -9999px";
 }
}
function addLoadEvent(func{
 var oldonload = window.onload;
 if (typeof window.onload != 'function') {
  window.onload = func;
 } else {
  window.onload = function({
   if (oldonload) {
    oldonload();
   }
   func();
  }
 }
}
addLoadEvent(preloader);

在该脚本的第一部分,获取使用类选择器的元素,并为其设置了background属性,以预加载不同的图片。

该脚本的第二部分,使用addLoadEvent()函数来延迟preloader()函数的加载时间,直到页面加载完毕。如果JavaScript无法在用户的浏览器中正常运行,图片不会被预加载,当页面调用图片时,正常显示即可。

仅使用JavaScript实现预加载

上述方法有时确实很高效,但它在实际实现过程中会耗费太多时间。

下面将提供两种这样的预加载方法,它们可以很漂亮地工作于所有现代浏览器之上。JavaScript代码段1 只需简单编辑、加载所需要图片的路径与名称即可,很容易实现:

<div class="hidden">
 <script type="text/javascript">
  <!--//--><![CDATA[//><!--
   var images = new Array()
   function preload() {
    for (i = 0; i < preload.arguments.length; i++) {
     images[i] = new Image()
     images[i].src = preload.arguments[i]
    }
   }
   preload(
    "http://domain.tld/gallery/image-001.jpg",
    "http://domain.tld/gallery/image-002.jpg",
    "http://domain.tld/gallery/image-003.jpg"
   )
  //--><!]]>
 
</script>
</div>

该方法尤其适用预加载大量的图片。画廊网站很适合使用该技术,将该脚本应用到登录页面,只要用户输入登录帐号,大部分画廊图片将被预加载。

JavaScript代码段2 该方法与上面的方法类似,也可以预加载任意数量的图片。将下面的脚本添加入任何Web页中,根据程序指令进行编辑即可。

<div class="hidden">
 <script type="text/javascript">
  <!--//--><![CDATA[//><!--
   if (document.images) {
    img1 = new Image();
    img2 = new Image();
    img3 = new Image();
    img1.src = "http://domain.tld/path/to/image-001.gif";
    img2.src = "http://domain.tld/path/to/image-002.gif";
    img3.src = "http://domain.tld/path/to/image-003.gif";
   }
  //--><!]]>
 
</script>
</div>

正如所看,每加载一个图片都需要创建一个变量,如img1 = new Image();,及图片源地址声明,如img3.src = "../path/to/image-003.gif";

参考该模式,可根据需要加载任意多的图片。

继续对该方法进行改进。将该脚本封装入一个函数中,并使用 addLoadEvent(),延迟预加载时间,直到页面加载完毕。

function preloader({
 if (document.images) {
  var img1 = new Image();
  var img2 = new Image();
  var img3 = new Image();
  img1.src = "http://domain.tld/path/to/image-001.gif";
  img2.src = "http://domain.tld/path/to/image-002.gif";
  img3.src = "http://domain.tld/path/to/image-003.gif";
 }
}
function addLoadEvent(func{
 var oldonload = window.onload;
 if (typeof window.onload != 'function') {
  window.onload = func;
 } else {
  window.onload = function({
   if (oldonload) {
    oldonload();
   }
   func();
  }
 }
}
addLoadEvent(preloader);

使用Ajax实现预加载

该方法利用DOM,不仅仅预加载图片,还会预加载CSS、JavaScript等相关的东西。

使用Ajax的优越之处在于JavaScript和CSS的加载不会影响到当前页面。该方法简洁、高效。

window.onload = function({
 setTimeout(function({
  // XHR to request a JS and a CSS
  var xhr = new XMLHttpRequest();
  xhr.open('GET''http://domain.tld/preload.js');
  xhr.send('');
  xhr = new XMLHttpRequest();
  xhr.open('GET''http://domain.tld/preload.css');
  xhr.send('');
  // preload image
  new Image().src = "http://domain.tld/preload.png";
 }, 1000);
};

上面代码预加载了“preload.js”、“preload.css”和“preload.png”。1000毫秒的超时是为了防止脚本挂起,而导致正常页面出现功能问题。下面,我们看看如何用JavaScript来实现该加载过程:

window.onload = function({

 setTimeout(function({

  // reference to <head>
  var head = document.getElementsByTagName('head')[0];

  // a new CSS
  var css = document.createElement('link');
  css.type = "text/css";
  css.rel  = "stylesheet";
  css.href = "http://domain.tld/preload.css";

  // a new JS
  var js  = document.createElement("script");
  js.type = "text/javascript";
  js.src  = "http://domain.tld/preload.js";

  // preload JS and CSS
  head.appendChild(css);
  head.appendChild(js);

  // preload image
  new Image().src = "http://domain.tld/preload.png";

 }, 1000);

};

这里,我们通过DOM创建三个元素来实现三个文件的预加载。正如上面提到的那样,使用Ajax,加载文件不会应用到加载页面上。


二、图片懒加载

这样做能防止页面一次性向服务器发送大量请求,导致服务器响应面,页面卡顿崩溃等。

实现思路

  • 首先正常渲染,但是真正的路径要放到data-src中去

  • 获取当前窗口高度,滚动高度

  • 滚动时进行判读,如果当前元素距离窗口高度小于窗口高度+滚动高度则将data-src中的数据放到src中(记得要加上防抖或者节流)

  • 删除data-src属性

  • 让n来控制循环开始数(防止滚回去还会操作src的问题)

  • 控制页面刷新后让滚动条复位(要加延迟否则可能不生效)

实现代码

jquery实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/jquery/2.1.0/jquery.min.js"></script>
    <style>
        .container {
            max-width800px;
            margin0 auto;
        }

        .container:after {
            content"";
            display: block;
            clear: both;
        }

    
</style>
</head>

<body>
    <div class="container">
        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1123/smalle51922e8a6da05ef69459dcbec14708a1606144751.jpg">

        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1123/smalledec4b73a9febffb61a0857b7a1221be1606131242.jpg">

        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1123/small7c61f7388b168e272835ec91ead277141606131144.jpg">


        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1120/small1d95e6a89ff1261aa812002b7c063e1e1605802066.jpg">

        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1119/small10dc593beaa5744ddcc5f84c778477691605801327.jpg">

        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1120/small6321d8199e10cbd5b53d69f4bb1b1a861605801612.jpg">

        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1123/small81f009e494f914e94eea3f51f5f824ef1606120977.jpg">

        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
            data-src="http://img.netbian.com/file/2020/1123/small5887a5aa6ff9e7b7932e89ef616d24251606131346.jpg">


    </div>

    <script>
        // 一开始没有滚动的时候,出现在视窗中的图片也会加载
        start();

        // 当页面开始滚动的时候,遍历图片,如果图片出现在视窗中,就加载图片
        let clock; //函数节流
        $(window).on('scroll'function ({
            if (clock) {
                clearTimeout(clock);
            }
            clock = setTimeout(function ({
                start()
            }, 200)
        })

        function start({
            $('.container img').not('[data-isLoading]').each(function ({
                if (isShow($(this))) {
                    loadImg($(this));
                }
            })
        }


        // 判断图片是否出现在视窗的函数
        function isShow($node{
            return $node.offset().top <= $(window).height() + $(window).scrollTop();
        }

        // 加载图片的函数,就是把自定义属性data-src 存储的真正的图片地址,赋值给src
        function loadImg($img{
            $img.attr('src', $img.attr('data-src'));

            // 已经加载的图片,我给它设置一个属性,值为1,作为标识
            // 弄这个的初衷是因为,每次滚动的时候,所有的图片都会遍历一遍,这样有点浪费,所以做个标识,滚动的时候只遍历哪些还没有加载的图片
            $img.attr('data-isLoading'1);
        }
    
</script>

</body>

</html>

原生js实现

使用scrollTop/innerHeight/offsetTop

基本知识点:

window.innerHeight:浏览器可视区域高度
document.body.scrollTop || document.documentElement.scrollTop:浏览器滚动条滚过高度
img.offsetTop:元素距文档顶部的高度 
面试官:图片加载优化中,你是如何实现预加载和懒加载的?
img

加载条件:

img.offsetTop < window.innerHeight + document.body.scrollTop;
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>LozyLoad</title>
 <style>
  .images{
   display: flex;
   flex-direction: column;
   text-align: center;
   width500px;
  }
  .img-item{
   height:400px;
   width400px;
   margin20px;
  }

 
</style>
</head>
<body>
 <div class="images">
  <img class="img-item" alt="loading" data-src="./img/img1.png">
  <img class="img-item" alt="loading" data-src="./img/img2.png">
  <img class="img-item" alt="loading" data-src="./img/img3.png">
  <img class="img-item" alt="loading" data-src="./img/img4.png">
  <img class="img-item" alt="loading" data-src="./img/img5.png">
 </div>
 <!--
 <script type="text/javascript">
  //获取观察器实例  changes是被观察的对象数组
  var observer = new IntersectionObserver(function(changes){  
   console.log(changes);
   changes.forEach(function(index,item){
    if(item.intersectionRatio > 0 && item.intersectionRatio < 1)
     //target:被观察的目标元素,是一个 DOM 节点对象
     item.target.src = item.target.dataset.src;
   });
  });
  function addObserver(){
   var listItems = document.querySelectorAll('.img-item');
   listItems.forEach(function(item){
    //实例的observe方法可以指定观察哪个DOM节点
    //开始观察  observe的参数是一个 DOM 节点对象
    observer.observe(item);
   });
  }
  addObserver();
 </script>
 -->

 <script type="text/javascript">
  var imgs = document.querySelectorAll('img');
  var lazyload = function(){
   var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
   var winTop = window.innerHeight;
   for(var i=0;i < imgs.length;i++){
    if(imgs[i].offsetTop < scrollTop + winTop ){
     imgs[i].src = imgs[i].getAttribute('data-src');
    }
   }
  }
  function throttle(method,delay){
   var timer = null;
   return function(){
    var context = this, args=arguments;
    clearTimeout(timer);
    timer=setTimeout(function(){
     method.apply(context,args);
    },delay);
   }
  }
  window.onscroll = throttle(lazyload,200);
 
</script>
</body>
</html>
使用IntersectionObserver方法

MDN介绍主页:https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

基本知识:

构造函数IntersectionObserver接收两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

这个构造函数的返回值是一个观察器实例。构造函数的返回值是一个观察器实例,实例的observe方法可以指定观察哪个DOM节点

observe的参数可以是一个DOM节点对象。如果要观察多个节点,就要多次调用这个方法。

callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员

intersectionRatio:目标元素的可见比例,完全可见时为1,完全不可见时小于等于0。
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>LozyLoad</title>
 <style>
  .images{
   display: flex;
   flex-direction: column;
   text-align: center;
   width500px;
  }
  .img-item{
   height:400px;
   width400px;
   margin20px;
  }

 
</style>
</head>
<body>
 <div class="images">
  <img class="img-item" alt="loading" data-src="./img/img1.png">
  <img class="img-item" alt="loading" data-src="./img/img2.png">
  <img class="img-item" alt="loading" data-src="./img/img3.png">
  <img class="img-item" alt="loading" data-src="./img/img4.png">
  <img class="img-item" alt="loading" data-src="./img/img5.png">
 </div>
 <script type="text/javascript">
  //获取观察器实例  changes是被观察的对象数组
  var observer = new IntersectionObserver(function(changes){  
   console.log(changes);
   changes.forEach(function(index,item){
    if(item.intersectionRatio > 0 && item.intersectionRatio < 1)
     //target:被观察的目标元素,是一个 DOM 节点对象
     item.target.src = item.target.dataset.src;
   });
  });
  function addObserver(){
   var listItems = document.querySelectorAll('.img-item');
   listItems.forEach(function(item){
    //实例的observe方法可以指定观察哪个DOM节点
    //开始观察  observe的参数是一个 DOM 节点对象
    observer.observe(item);
   });
  }
  addObserver();
 
</script>
</body>
</html>


原文始发于微信公众号(豆子前端):面试官:图片加载优化中,你是如何实现预加载和懒加载的?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56999.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!