JavaScript实现图片懒加载非可视区域延迟加载技术
网页上图片很多时,如果不对图片使用懒加载(延迟加载)技术,网站显示速度就会很慢,给用户的感觉很不好,图片资源服务器的负载也会很大,一般不太好的服务商会直接返回 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 上面文章里面的正则是为了考虑到有时候 Img 标签中有换行,如果你的网页中的 Script 标签被影响了,可以尝试修改为下面的试试:
如果还有问题,欢迎留言。
抱歉打扰博主了,我现在已经不会写正则了。博主的正则有个bug,会把源码内第一个<script>的src修改,如果博主看到希望能写上正确的正则,非常感谢。
哦不我的也错了,看来是要补补正则了
正则出错了,害我找了半天错误
@魔方的爱情,这个在使用之前需要将网页中图片的 src 属性全部替换成 osrc,这样浏览器就不会自动加载了。这个懒加载功能在本站中已经使用过了,目前没有发现一次性加载完成。
哥们呀,这个我测试只有ie7有效,其它全无效,8.10,9全是一次性加载完成