[译] 懒加载图片?不要依赖 JavaScript !

阅读 5770
收藏 154
2016-07-14
原文链接:www.jianshu.com

现在许多网页都包含加载图片, 例如:只需访问你最喜欢的购物网站,并通过产品列表页滚动。

正如你想象, 加载页面时引入所有的图片会带来不必要的臃肿, 使用户下载大量他们可能无法看到的数据, 也让页面交互变得很慢, 由于新图片载入会使得页面布局经常发生变化,从而导致浏览器重新加载页面。

一个流行的解决办法是”懒加载”这些图片,就是说, 在用户需要看到图片之前才去加载它们。

如果这项技术应用到‘头版头条’–也就是页面的第一视图部分,用户能得到明显更快的首页浏览体验。

因此每个人都应该这样做吗?

在回答这个问题之前,来看看通常是如何实现的。很容易就能找到一个合适的jQuery插件/angularjs模块,然后运行一条简单的安装命令,接下来的你都会做了,只需给image标签添增加一个新属性,或者是用JavaScript函数来处理延迟加载的图片。

综上所述,是不是特别容易?

我们想要达到的效果是,可以只使用html在web页面显示图片,也可以在有其他工具的时候延迟图片显示。 jquery/angularjs的解决方案都对JavaScript、jquery和angularjs有依赖,如果浏览器不支持JavaScript呢? 如果用户不想仅仅为了实现图片懒加载而下载这些臃肿的库呢?

如果有一些浏览器工具、扩展、插件、广告等等有JavaScript错误时,你的用户就不能看到网页上的图片了,似乎不太机智吧?

逐步替换为懒加载图片

鉴于潜在的局限性,让我们搞一个能解决我全部的问题的方案:

a. 去JavaScript化(即:懒加载是一种增强手段)

b. vanilla js - 不依赖jquery/angularjs

c. 可以在JavaScript失效的情况下运作(即:浏览器是支持JavaScript的,但有一个地方出现了JavaScript错误,让你的脚本损坏了,甚至可能不是你的错!)

顺着这个逻辑,用一个数据属性在一个图片元素上是有道理的,并且当元素越来越接近视图时,把src属性和data-src属性进行交换,就像这样:

Laziness

然后JavaScript代码是这样的:

var lazy = document.getElementsByClassName('lazy');

for(var i=0; i

a) 去JavaScript化

看起来是合理的第一步,接着我们怎么才能修改到支持去JavaScript化? 随着一些html的重复,将成为可能:

Laziness


这意味着如果JavaScript被禁用,懒加载将被忽略。我在网络使用情况中,对上述代码做了快速检查。可以确认使用这些基本的 noscript img 代码不会导致多个请求!
你不得不承认,这值得一试!

b) 去jQuery/angularjs化

依靠上面的HTML代码,我们可以写出下面的JavaScript函数,去做 data-srcsrc 的切换:

function lazyLoad(){
    var lazy =
    document.getElementsByClassName('lazy');

    for(var i=0; i

然后,让我们创建一个简单的事件连接起来做跨浏览器支持(因为我们没有使用jQuery):

function registerListener(event, func) {
    if (window.addEventListener) {
        window.addEventListener(event, func)
    } else {
        window.attachEvent('on' + event, func)
    }
}

接着当页面加载时,注册 lazyload 函数去执行.

registerListener('load', lazyLoad);

现在当页面加载时我们从 lazy 类获取到所有图片,并且用JavaScript去加载,这当然会延迟加载,但不够聪明。

听起来像是我需要一些视图逻辑,像这样(片段来自于StackOverflow):

function isInViewport(el){
    var rect = el.getBoundingClientRect();

return (
    rect.bottom >= 0 && 
    rect.right >= 0 && 

    rect.top <= (="" window.innerheight="" ||="" document.documentelement.clientheight)="" &&="" rect.left="" <="(" window.innerwidth="" document.documentelement.clientwidth)="" );="" }="" code="">

还需要增加视图检查:

function lazyLoad(){
    var lazy = 
    document.getElementsByClassName('lazy');

    for(var i=0; i

同时注册 scroll 事件:

registerListener('scroll', lazyLoad);

注意, 这是 不好的 , 你不应该当用户在滚动时改变页面。这是为了懒加载的示例实现,请有空时来改善它!

现在我们有了一个只会在视图内加载图片的页面,如果JavaScript被禁用,也会正常加载所有图片

相关文档: codepen.io/rposbo/pen/…

重构技巧

在下一个需求(支持损坏的JavaScript)之前, 我想整理一下代码。会在每个滚动事件中检查所有懒加载图片, 即使这些图片已经被加载出来

这在我的demo里不是个大问题,但在图片更多的时候不是最好的办法,而且感觉很乱!我想从 lazy 数组中移除已经加载完毕的图片。

首先,移动 lazy 数组到一个共享变量中,在一个载入时就被调用的函数中设置:

var lazy = [];

function setLazy(){
 lazy = document.getElementsByClassName('lazy');
}

registerListener('load', setLazy);

Ok, 现在我们有包含全部懒加载图片的数组了,但是我需要保持数据是最新的,然后移除使用过的 data-src 属性,接着过滤所有图片:

function lazyLoad(){
    for(var i=0; i

这样感觉好多了,现在 lazy 数组将永远只包含那些尚未加载的图像。但是正如前面提到的, onscroll 事件中做了大量工作。

这个版本在这里: codepen.io/rposbo/pen/…

c) Broken JavaScript

我喜欢这个需求,棘手的挑战才有乐趣。如果浏览器支持JavaScript, noscript 标签会被忽略掉,可是,如我开始提到的, 浏览器可能因为某些原因不会执行JavaScript代码。

下面的怎么样?

  1. 在窗口中添加足够多的图片填满视图,即只用 imgsrc 属性设置。
  2. 在这些图像有链接到一个新页面,是 未懒加载 的 - 整个页面用 标签填充。
  3. 用css隐藏所有 lazy 图片。
    4。 使用JavaScript移除链接,并且移除css隐藏的 lazy 图片。

这样想想:如果界面加载和JavaScript中断了,用户会看到满屏图片(1)并且有一个链接去“查看更多”(2),点击后会进入完整的页面(从他们离开的地方)。

如果界面加载和JavaScript都是OK的,这个链接就不会在那(4),懒加载图片会随之进入视图。

让我们来尝试一下。您可以使用自己网站的分析,看看普通用户的分辨率是什么,以及计算有多少子元素都适合在其初始视图,以决定把这个“查看更多”链接放在哪里(2):


假如 flatpage.html 只是一个非懒加载版本的同一页,用在项目列表中的相同点的元素。

现在开始隐藏懒加载图片(3),在周围增加了一个新元素:


这个类的css代码:

 .hidden {display:none;}

下面代码会捕获Javascript代码运行出错的用户,并显示一个初始视图,还有一个到整个界面的链接。
JavaScript代码正常的用户将会重新启用懒加载,我在 setLazy 函数做这些逻辑(4):

// delete the view more link
document.getElementById('listing')
    .removeChild(
        document.getElementById('viewMore')
    );

// display the lazy items
document.getElementById('nextPage')
    .removeAttribute('class');

最终代码:



    
        .item {width:300px; display: inline-block; }
        .item .itemtitle {font-weight:bold; font-size:2em;}
        .hidden {display:none;}
    


    

Amalgam Comics Characters
Dark Claw Dark Claw
Super Soldier Super Soldier
Spider Boy Spider Boy
View more
Iron Lantern Iron Lantern Iron Lantern
Amazon Amazon Amazon
Bizarnage Bizarnage Bizarnage
Catsai Catsai Catsai
Moonwing Moonwing Moonwing
Hawkeye Hawkeye Hawkeye
Mercury Mercury Mercury
Dr. Strangefate Dr. Strangefate Dr. Strangefate var lazy = []; registerListener('load', setLazy); registerListener('load', lazyLoad); registerListener('scroll', lazyLoad); registerListener('resize', lazyLoad); function setLazy(){ document.getElementById('listing').removeChild(document.getElementById('viewMore')); document.getElementById('nextPage').removeAttribute('class'); lazy = document.getElementsByClassName('lazy'); console.log('Found ' + lazy.length + ' lazy images'); } function lazyLoad(){ for(var i=0; i= 0 && rect.right >= 0 && rect.top

实时预览代码: codepen.io/rposbo/pen/…

总结

正如你看到的,的确实现了懒加载图片(包括你想了解的其他内容),同时对损坏的JavaScript和不支持JavaScript的条件下进行了兼容。

这有一个GitHub仓库作为实践,展示了主页面和“flat”列表页的区别:github.com/rposbo/lazy…

此仓库还展示了在.NET中实现的解决方案,通过相同的动态生成items到两个列表页。

评论