LMLPHP后院

JavaScript实现图片懒加载非可视区域延迟加载技术

maybe yes 发表于 2014-12-13 18:24

网页上图片很多时,如果不对图片使用懒加载(延迟加载)技术,网站显示速度就会很慢,给用户的感觉很不好,图片资源服务器的负载也会很大,一般不太好的服务商会直接返回 503 Service Unavailable 暂停服务错误。很多网站在处理这个问题时,直接使用 JQuery 的懒加载扩展,这样一来,网站必须要加载 JQuery,速度自然下降。JQuery 是一个很臃肿的 JavaScript 框架,虽然非常强大,但我个人认为不适合做前端,只适合做后台。网站前端的用户一般都不是很经常来,第一次来就要加载这么多的资源,给用户感觉网站非常慢。大部分的网站都是小网站,流量都很小,网站也都是运行在虚拟主机上,这样一来,JQuery 放在自己的站点上流量耗费很大;使用免费的 CDN 也通常不太稳定,并且浏览器得重新解析新的域名,网站速度自然上不去。

在这种情况下,LML 团队打造的开源 JavaScript 框架 LMLJS 很好的解决了这个问题,LMLJS 现在体积大小约为5KB,压缩后大概2KB。LMLJS 内置 Deferred 对象,实现了对网页图片资源,CSS 含图片的延迟加载,内置动态加载 JS 文件方法,方便获取 JavaScript 资源,内置 getElementsByClassName 方法方便更好的选择 DOM 节点。LMLJS 将不断完整,在保持很小的体积的同时增加实用的功能,并兼容 IE6+,Firefox,Chrome 等主流浏览器。LMLJS 项目地址:https://github.com/leiminglin/LMLJS。想了解更多,请看我的另一篇文章《开源JavaScript框架LMLJS发布》。关于LMLJS的最新动态,请关注我们的官方博客。

下面将讲解使用纯 JavaScript 如何实现对网页图片在可视区域范围内加载技术,我们称之为“图片懒加载技术”或者“图片延迟加载技术”。

首先,要实现这项技术,我们需要将服务端输出的网页中的所有的 IMG 标签的 SRC 属性给屏蔽掉,只有屏蔽掉了 SRC 属性,图片就不会自动被加载,关于如何将图片中的 SRC 属性去掉,在静态页面上,可以手动的去掉 IMG 标签的 SRC 属性;在动态的内容上,特别是这些 HTML 内容在数据库中已经存在了,我们可以在读取时将图片标签内容进行替换处理。下面以 PHP 为例,替换图片标签 SRC 属性的正则代码:

<?php 
preg_replace('/<img([\s\S]+?)src\s*=([\s\S]+?)>/i', "<img$1osrc=$2>", $content);
?>

上面的代码将图片中的 src 属性替换成了 osrc,关于该替换成什么,没有特别的规定,可以根据个人爱好来设定,目前在网上见过比较多的是替换成“data-src”。在这里需要特别强调,有些站点可能会在替换 src 后,仍然使用一个默认的图片地址作为 src,但我个人认为这样可能对搜索引擎不友好,导致图片没有被收录。关于控制网页中图片大小,本人建议最好使用 DIV 标签来对图片做个包裹,大小和图片标签中的 width 和 height 属性一致,这个技术也可以通过正则替换实现,这里就不举例了。关于为什么要包裹 IMG 标签呢?是因为有部分浏览器,比如 Firefox,当图片没有 SRC 属性时,图片的 width 和 height 属性是无效的,图片被显示成很小的区域,这样在懒加载时,页面的高度会不断的发生变化,这样对用户的感觉很不好。

完成了对 IMG 标签 SRC 属性的改变后,我们需要在页面 OnLoad 事件发生后对页面中所有的图片进行遍历,分析每个图片的位置是否在可见范围内,若在可视区域内,则开始加载这个图片。同时需要侦听页面的滚动事件和窗口大小改变事件,当页面发生滚动时,需要再次进行遍历页面中未被加载的图片并计算图片位置,若图片出现在可见范围内,则加载这个图片,当窗口大小发生变化时,需要再次计算窗口大小。当所有图片加载完成后移除页面滚动事件。下面将逐一分解实现的过程:

下面的代码计算元素距离可见区域顶部的值,此方法系 LMLJS 框架内置方法,方法返回元素至可见区域顶部的距离的值,支持隐藏元素,若元素为隐藏时,则返回其父级元素的值。

<script>
function getElementViewTop(element){
    var actualTop = element.offsetTop
        ,offsetParentElement = element.offsetParent;
    if( offsetParentElement == null && element.parentNode ){
        /* when style display is none */
        var parentNode = element.parentNode;
        while( offsetParentElement == null ){
            offsetParentElement = parentNode.offsetParent;
            parentNode = parentNode.parentNode;
            if( !parentNode ){
                break;
            }
        }
    }
    while ( offsetParentElement !== null /* document.body */ ){
        actualTop+=(offsetParentElement.offsetTop+offsetParentElement.clientTop);
        offsetParentElement = offsetParentElement.offsetParent;
    }
    var elementScrollTop = document.documentElement.scrollTop 
        ? document.documentElement.scrollTop : document.body.scrollTop; 
    return actualTop - elementScrollTop;
}
</script>

下面的代码计算窗口大小的值,此方法系LMLJS内置方法,返回当前可见浏览器窗口的大小。为了让懒加载更加准确,我们需要侦听窗口大小改变事件,在窗口改变大小后,需要重新计算窗口大小。但是某些浏览器,比如UC浏览器,不遵守 onresize 事件,UC 浏览器顶部的地址栏会经常的隐藏和显示,其实这样就改变了窗口的大小,但是没有触发 onresize 事件,导致页面最底部相当于 UC 浏览器地址栏高度的区域图片不能加载,在其他手机浏览器上测试都没有这个问题。一般来说,网站底部有图片的也很少见。

<script>
function getViewport(){
    if( document.compatMode == "BackCompat" ){
        return { width:document.body.clientWidth, 
            height:document.body.clientHeight }
    }else{
        return { width:document.documentElement.clientWidth, 
            height:document.documentElement.clientHeight }
    }
}
</script>

下面的代码系对网页中图片进行遍历并加载的核心代码,来自 LMLJS 框架。代码实现了当图片在可见范围时,将 IMG 的 osrc 属性改变为 src 属性,如果加载失败则尝试再次加载 osrc-bak 属性的图片地址。同时侦听页面滚动事件,直到图片全部加载完成。LMLJS 下期更新会增加注册图片处理方法功能,这样 LMLJS 框架在开始加载图片时会调用用户自定义的方法,实现更多的需求。

<script>
/**
 * Lazy load img
 */
deferred.then(function(){
    var i, length, src, m = doc.getElementsByTagName('IMG'), 
        viewport=getViewport(), count=0;
    window.onresize = function(){
        viewport = getViewport();
    };
    var loadImg = function(){
        if( count >= m.length ){
            /* remove event */
            if( window.addEventListener ){
                document.removeEventListener( 'scroll', loadImg, false );
            }else if( window.attachEvent ){
                window.detachEvent(event, loadImg); 
            }
            return;
        }
        for(i=0,j=m.length; i<j; i++){
            if( m[i].getAttribute('src') ){
                continue;
            }
            var viewtop = getElementViewTop(m[i])
                ,imgHeight = m[i].getAttribute('height')||0;
            if( viewtop>=-imgHeight && viewtop < viewport.height 
                && (src=m[i].getAttribute('osrc')) ){
                m[i].setAttribute('src',src);
                m[i].onerror=function(){
                    if( src=this.getAttribute('osrc-bak') ){
                        this.setAttribute('src',src);
                        this.onerror=null;
                    }
                };
                m[i].onload = function(){
                    count++;
                }
            }
        }
    };
    
    if( window.addEventListener ){
        document.addEventListener( 'scroll', loadImg, false );
    }else if( window.attachEvent ){
        window.attachEvent("onscroll", function(){
            loadImg();
        }); 
    }
    loadImg();
    deferred.promise();
});
</script>

全文完,欢迎各位网友交流。

相关文章
评论列表
Rabbit

非常感谢

maybe yes

@Rabbit 上面文章里面的正则是为了考虑到有时候 Img 标签中有换行,如果你的网页中的 Script 标签被影响了,可以尝试修改为下面的试试:


preg_replace('/<img([^<>]+?)src\s*=([^<>]+?)>/i', "<img$1osrc=$2>", $content);

如果还有问题,欢迎留言。

Rabbit

抱歉打扰博主了,我现在已经不会写正则了。博主的正则有个bug,会把源码内第一个<script>的src修改,如果博主看到希望能写上正确的正则,非常感谢。

Rabbit

哦不我的也错了,看来是要补补正则了

Rabbit

正则出错了,害我找了半天错误

maybe yes

@魔方的爱情,这个在使用之前需要将网页中图片的 src 属性全部替换成 osrc,这样浏览器就不会自动加载了。这个懒加载功能在本站中已经使用过了,目前没有发现一次性加载完成。

魔方的爱情

哥们呀,这个我测试只有ie7有效,其它全无效,8.10,9全是一次性加载完成

LMLPHP,可爱滴WEB开发框架

2017-11-19 05:38:58 1511041138 0.007049