懒加载和预加载的实现

/ javascript / 没有评论 / 2222浏览

懒加载和预加载的实现

提到前端性能优化中图片资源的优化,懒加载和预加载就不能不说,下面我用最简洁明了的语言说明懒加载和预加载的核心要点以及实现

懒加载

什么是懒加载

懒加载也就是延迟加载;当访问一个页面时,先将img标签中的src链接设为同一张图片(这样就只需请求一次,俗称占位图),将其真正的图片地址存储在img标签的自定义属性中(比如data-src);当js监听到该图片元素进入可视窗口时,即将自定义属性中的地址存储到src属性中,达到懒加载的效果;这样做能防止页面一次性向服务器响应大量请求导致服务器响应慢页面卡顿或崩溃等问题

为什么要使用懒加载

懒加载对于图片较多页面很长的业务场景很适用,可以减少无效资源的加载

懒加载的实现步骤

  1. 首先,不要将图片地址放到src属性中,而是放到其它属性(data-src)中
  2. 页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中
  3. 在滚动事件中重复判断图片是否进入视野;如果进入,则将data-original属性中的值取出存放到src属性中

代码实现

既然懒加载的原理是基于判断元素是否出现在窗口可视范围内,首先我们写一个函数判断元素是否出现在可视范围内:

<script>
function isVisible($node){
    var winH = $(window).height(),
        scrollTop = $(window).scrollTop(),
        offSetTop = $(window).offSet().top;
    if (offSetTop < winH + scrollTop) {
        return true;
    } else {
        return false;
    }
}
</script>

再添加上浏览器的事件监听函数,让浏览器每次滚动就检查元素是否出现在窗口可视范围内:

<script>
$(window).on("scroll", function{
    if (isVisible($node)){
        console.log(true);
    }
})
</script>

现在我们要做的是,让元素只在第一次被检查到时打印true,之后就不再打印了

<script>
var hasShowed = false;
$(window).on("sroll",function{
    if (hasShowed) {
        return;
    } else {
        if (isVisible($node)) {
            hasShowed = !hasShowed;
            console.log(true);
        }
    }
})
</script>

这样我们就实现了懒加载。利用懒加载和AJAX,我们还可以实现无限滚动查看时间线,在滚动页面一段距离后出现回到顶部按钮的效果

懒加载的优点

显著的提高页面加载速度,又不下载多余的资源节省了流量;同时更少的图片并发请求数也可以减轻服务器的压力

懒加载插件

关于图片延时加载,网上有很多应用的例子以及插件;目前研究过的两个插件分别是jquery插件lazyload.js和原生js插件echo.js;二者的区别不用说,jquery插件使用的时候必须引入jquery才可以,且该插件功能强大,灵活性也高;而echo.js是用原生写的插件,代码少,不依赖其他库,拿起来就可以用,但能够实现的效果不如lazyload丰富强大,但基本的延时加载要求都能满足。

jquery.lazyload.js

如何使用

延迟加载依赖于于jQuery,第一步引入文件:

<script src ="jQuery.js"></script>
<script src="jQuery.lazyload.js"></script>

接下来修改html的一些属性:图像的src地址暂时存储在自定义属性data-original中,然后给需要延时加载图像加上一个特定的类,类的名字由你自己决定,使用的时候统一类名即可;为这些图片绑定延时加载:

<img class="lazy" src="img/grey.gif" data-original="img/example.jpg"  width="640" heigh="480">

用的时候就像下面:

$("img.lazy").lazyload();

所有class为lazy的图片将被延迟加载。注意:必须设置图像的尺寸,宽度和高度属性或CSS,否则插件可能无法正常工作。

参数设置

  1. 设置阈值 默认情况下图片在位于可视区域后才开始加载;如果想提前加载图片,可通过设置threshold的值来改变其显示的时间,设置threshold为200使图片在距离屏幕可见区域下方200像素时就开始加载
$("img.lazy").lazyload({
  threshold:200
});
  1. 事件触发加载 默认是scoll事件触发延时加载,即等到用户向下滚动至图片出现在屏幕可视区域时图片才能加载,但可以使用jQueryclick或mouseover等事件触发图片的加载,也可以使用自定义事件,实现只有当用户点击图片图片才能够加载时可以这样写:
$("img.lazy").lazyload({
    event : "click"
});

注意:你也可以使用这个技巧延迟图片加载,即加载前延迟5秒后再加载图片;就像下面这样(trigger()方法触发被选元素的指定事件类型):

$(function() {
    $("img.lazy").lazyload({
        event:"click"
    });
});
$(window).bind("load", function() {
    var timeout = setTimeout(function() {
        $("img.lazy").trigger("click") //trigger()方法触发被选元素的指定事件类型
    }, 5000);
});
  1. 使用特殊效果加载图片 插件默认使用show()方法显示图片;当然你可以使用任何你想用的特效来处理,例如使用fadeIn效果:
$("img.lazy").lazyload({ 
    effect : "fadeIn" //.effect()方法对一个元素应用了一个命名的动画 特效
});
  1. 为非JavaScript浏览器回退
<img class="lazy" src="img/grey.gif" data-original="img/example.jpg"  width="640" heigh="480">
<noscript><img src="img/example.jpg" width="640" heigh="480"></noscript>

可以通过CSS隐藏占位符

.lazy {
  display: none;
}

在支持JavaScript的浏览器中必须在DOM ready时将占位符显示出来,这可以在插件初始化的同时完成。

$("img.lazy").show().lazyload();

这些都是可选的,但如果你希望插件平稳降级这些都是应该做的

  1. 图片内容器 可以将插件用在可滚动容器的图片上,例如带滚动条的DIV元素;将容器定义为jQuery对象并作为参数传到初始化方法里面
#container {
    height: 600px;
    overflow: scroll;
}
$("img.lazy").lazyload({         
     container: $("#container")
}); 
  1. 当图像并不是连续的 滚动页面时,Lazy Load会循环加载图片;在循环中检测图片是否在可视区域内,默认情况下在找到第一张不在可见区域的图片时停止循环;图片被认为是流式分布的,图片在页面中的次序和HTML代码中次序相同;但是在一些布局中,这样的假设是不成立的;不过你可以通过failurelimit选项来控制加载行为
$("img.lazy").lazyload({ 
    failure_limit : 10
}); 

将failurelimit设为10令插件找到10个不在可见区域的图片是才停止搜索

7.处理看不见图像 可能在你的页面上有很多隐藏的图片;为了提升性能,Lazy Load默认忽略了隐藏图片;如果想要加载隐藏图片,将skip_invisible设为 false:

$("img.lazy").lazyload({ 
    skip_invisible : false
}); 

echo.js

兼容性:Echo.js使用了HTML5的date属性,并且需要获取该属性的值,所以它并不兼容IE6/IE7。使用方法:

引入文件

<script src="js/echo.min.js"></script> 

HTML

<img src="images/blank.gif" alt="pic" data-echo="img/pic.jpg" width="640" height="480">

blank.gif用做默认图片,data-echo的属性值是图片的真实地址,同样最好给图片设置宽度和高度

JavaScript

echo.init({
    offset: 0,
    throttle: 0
});

参数说明

echo.js只有两个参数:offset和throttle

那么上面的代码的意思就是一旦图片进入可视区域就立即加载。怎么样,使用起来真的很简单吧

预加载

什么是预加载

提前加载图片,当用户需要查看时可直接从本地缓存中渲染

为什么要使用预加载

图片预先加载到浏览器中,访问者可顺利地在网站上冲浪,并享受到极快的加载速度;这对图片占据很大比例的网站来说十分有利,保证了图片快速/无缝地发布,也可帮助用户在浏览网站内容时获得更好的用户体验。预加载的核心要点如下:

实现预加载主要有三个方法

用CSS和JavaScript实现预加载

实现预加载图片有很多方法,包括使用CSS/JavaScript/两者的各种组合,这些技术可根据不同设计场景设计出相应的解决方案,十分高效。

  1. 单纯使用CSS,可容易/高效地预加载图片,代码如下:
#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; }  
#preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; }  
#preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }

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

function preloader() {  
    if (document.getElementById) {  
        document.getElementById("preload-01").style.background = 
                "url(http://domain.tld/image-01.png) no-repeat -9999px -9999px";  
        document.getElementById("preload-02").style.background = 
                "url(http://domain.tld/image-02.png) no-repeat -9999px -9999px";  
        document.getElementById("preload-03").style.background = 
                "url(http://domain.tld/image-03.png) 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无法在用户的浏览器中正常运行,会发生什么?很简单,图片不会被预加载,当页面调用图片时,正常显示即可.

  1. 仅使用JavaScript实现预加载 上述方法有时确实很高效,但我们逐渐发现它在实际实现过程中会耗费太多时间;相反,我更喜欢使用纯JavaScript来实现图片的预加载;下面将提供两种这样的预加载方法,它们可以很漂亮地工作于所有现代浏览器之上
<div class="hidden">  
    <script type="text/javascript">   
            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>

该方法尤其适用预加载大量的图片

<div class="hidden">  
    <script type="text/javascript"> 
            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);
  1. 使用Ajax实现预加载 上面所给出的方法似乎不够酷,那现在来看一个使用Ajax实现图片预加载的方法;该方法利用DOM,不仅仅预加载图片,还会预加载CSS/JavaScript等相关的东西;使用Ajax比直接使用JavaScript优越之处在于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加载文件不会应用到加载页面上;从这点上看,Ajax方法优越于JavaScript

补充知识

屏幕可视窗口大小

<script>
  //原生方法
  window.innerHeight || //标准浏览器及IE9+
  document.documentElement.clientHeight || //标准浏览器及低版本IE标准模式
  document.body.clientHeight  //低版本混杂模式
  //jQuery方法 
  $(window).height();
</script>

滚动条滚动的距离

<script>
  //原生方法
  window.pagYoffset || //标准浏览器及IE9+
  document.documentElement.scrollTop || //兼容ie低版本的标准模式
  document.body.scrollTop //兼容混杂模式;
  //jQuery方法 
  $(document).scrollTop();
</script>

获取元素的尺寸

<script>
  $(o).width() = o.style.width;
  $(o).innerWidth() = o.style.width+o.style.padding;
  $(o).outerWidth() = o.offsetWidth = o.style.width+o.style.padding+o.style.border;
  $(o).outerWidth(true) = o.style.width+o.style.padding+o.style.border+o.style.margin;
</script>

注意: 要使用原生的style.xxx方法获取属性,这个元素必须已经有内嵌的样式,如<div style="...."></div>;如果原先是通过外部或内部样式表定义css样式,必须使用o.currentStyle[xxx] || document.defaultView.getComputedStyle(0)[xxx]来获取样式值

获取元素的位置信息

<script>
  //原生方法
  getoffsetTop();
  //jQuery方法 
  $(o).offset().top //元素距离文档顶的距离
  $(o).offset().left //元素距离文档左边缘的距离
  // 顺便提一下返回元素相对于第一个以定位的父元素的偏移距离,注意与上面偏移距的区别;
  jQuery:position() //返回一个对象
  $(o).position().left = o.style.left;
  $(o).position().top = o.style.top;
</script>