目前,出于性能与灵活性方面的考虑,咱们都将一些小图片替换成矢量图或者字体。除了这些能被替换的小图以外,还有一些不得不使用位图的场景,如照片、背景等。对于这些位图,咱们须要考虑它们在加载过程当中的不一样状态,而制定不一样的表现方案。javascript
图片加载过程当中的状态,大体包括:css
什么时候决定加载图片html
加载中java
加载结束,失败的处理css3
本文将对比加载过程当中的各个状态表现的不一样实现方案,并对方案的简易程度、兼容性、可扩展性进行分析。web
通常状况,页面加载时图片资源做为单独的请求向服务器获取。有时,因为图片的数量与大小的影响,这致使页面加载缓慢,并且加载了许多用户未触发的图片,也白白浪费了流量;有时,又须要保证用户浏览网页的流畅性,不得不预先加载好图片,等用户触发显示图片。ajax
可见,正确的图片加载时机对提升用户体验有很大的帮助。本小节将讨论与图片加载时机相关的技术:预加载与懒加载。浏览器
预加载技术就是<u>在用户触发图片显示以前,先将图片加载到本地</u>。当用户触发图片显示的事件时,浏览器可以及时渲染出图片,保证了用户浏览网页的流畅性。那么,如何实现预加载呢?本文将介绍三种实现预加载的技术。服务器
该方法只需<u>将欲加载的图片url写入某个隐藏元素的background属性中</u>,让这张图片和CSS文件同时加载。当用户触发显示图片的事件时,再将图片插入到目标位置。wordpress
以下代码所示,这里用到CSS3的background多图片特性,只需一个隐藏元素便可预加载全部图片。
.nothing-1 { display: none; background: url('1.jpg'), url('2.jpg'), url('3.jpg'); }
(关于background多图片特性的兼容性:http://caniuse.com/#search=cs... )
该方法的缺点在于没法控制预加载的时机,只能是页面加载时一块儿加载图片,若是图片过多,会阻塞页面的load事件,延长页面加载时间。
第二种实现预加载的技术,是<u>直接使用JavaScript新建Image对象</u>,对其src属性赋值。
以下代码所示:
var image = new Image(); image.src = '...img url'; image.onload = function() { ... } image.onerror = function () { ... }
还能够利用Image对象的load事件和error事件,完成图片预加载和失败以后的处理,如全部图片加载完成后的提示等。
最后这种方法则是<u>直接发送ajax请求,获取图片资源</u>。一样,可将ajax请求成功与失败的回调,做为图片预加载成功与失败的处理。
以下代码所示(仅针对webkit浏览器):
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { ... } } } xhr.open("GET", '...img url'); xhr.send('');
Ajax请求方法无需多余的Image对象,并且<u>获取的资源也不只局限于图片</u>。
懒加载的核心思想是<u>根据用户须要加载图片资源,并不是在渲染页面时就获取全部图片</u>。如此,不只能够减轻服务器的压力节省用户流量,还能够提升页面加载速度。
首先,将页面中全部图片url写进img的'data-src'属性,并将默认的loading图片写入src,做为加载中的图片样式。
<img src="loading.gif" data-src="...image url" />
其次,是肯定判断图片加载的时机,即当图片进入浏览器的可视区域中时。那么问题来了,如何判断某个元素进入可视区域呢?
以垂直方向为例,判断元素是否进入可视区,首先咱们须要知道:
可视区域的高度:window.innerHeight(IE9之前不可用)|| document.documentElement.clientHeight (IE标准模式);
元素相对于可视区域顶端位置:element.getBoundingClientRect().top;
元素相对于可视区域底端位置:element.getBoundingClientRect().bottom;
判断是否进入可视区,即<u>判断元素的顶部相对于可视区域顶端位置是否大于0,且又小于可视区域高度;或者,元素的底部相对于可视区域底端位置是否大于0,且又小于可视区域高度</u>。
上图中列出了元素进入可视区的全部情形,从上到下来看,第一个元素的top与bottom的值都小于0,不在可视区内;第二个元素的top小于0,而bottom却在0到clientHeight之间,已在可视区内,须要加载;第三个元素的top与bottom都知足条件,在可视区中;剩下的两个元素与头两元素状况一致。
等到元素进入可视区域,就将img元素的'data-src'属性赋值给'src'属性,并标记该img元素正在加载,再也不作此处理。
以上过程伪代码以下:
function check() { imgs.forEach(function(img, index) { if (loadedList.indexOf(index) >= 0) { return; } if (!isInClient(container)) { return; } loadedList.push(index); loadImage(img); }) }
最后,将判断图片进入可视区域后加载的check函数,<u>写入window的scroll和resize事件便可</u>。
有时用户若只想查看排序较后的图片,会快速滚动滚动条,这种状况下快速滚动过的图片也没有必要加载了。
该优化的实现思路也很简单,<u>延迟加载逻辑,计时器一到再判断元素是否还在可视区域</u>,可将代码修改成以下:
function check() { imgs.forEach(function(img, index) { if (loadedList.indexOf(index) >= 0) { return; } if (!isInClient(container)) { return; } setTimeout(function() { if (!isInClient(container)) { return; } loadedList.push(index) loadImage(img) }, 1000); }) }
图片出于加载中的状态时,须要给用户“图片正在加载”的提示,图片加载完成后,将提示隐藏。下面,本文将给出加载中提示的两种实现方案。
此方案的实现思路是:<u>将“加载中”提示做为img的背景,当图片正在加载中时,页面显示的是背景;图片加载完成,则覆盖了背景显示出图片</u>。
关键代码:
<img class="img" src="..."> .img { width: 500px; height: 300px; background: url('data:image/gif;base64,...') center no-repeat; }
这里将“加载中”的动态图片做为img标签的background,而且转换base64编码,这样能够减小一次请求。
浏览器兼容状况见下表:
浏览器 | Chrome 54 | Safari 10.0 | Firefox 50.0 | IE 8 |
---|---|---|---|---|
兼容性 | √ | √ | √ | √ |
兼容性没有问题,惟一的限制是须要设定.img的宽高。若事先没法获得图片的宽高,则能够在img标签外套一层<div class="img">
,由外层容器的background做为“加载中”提示,容器中的img可根据容器的宽或高设定其大小,关键代码以下:
<div class="img"> <img src="..."> </div> img { height: 100%; }
<u>img元素的load事件会在图片加载完成后触发</u>,因此咱们能够利用这一特色,进行加载中提示。
实现思路:<u>首次渲染img元素时,将“加载中”提示做为img元素,真正的图片则用另外一个Image对象加载。等到真正的图片加载完成,即触发了Image对象的load事件,则将img元素的src修改成真正图片的url</u>。
关键代码以下:
Array.from(document.getElementsByTagName('img')).forEach(img => { const image = new Image(); image.src = img.dataset.src; image.onload = () => img.src = image.src; })
这种方法须要js配合,不管是加载中的提示动画仍是图片均可以原始尺寸展现,而不须要再设置。
兼容性也毫无问题:
浏览器 | Chrome 54 | Safari 10.0 | Firefox 50.0 | IE 8 |
---|---|---|---|---|
兼容性 | √ | √ | √ | √ |
上面介绍的两种方案都能实现“加载中”提示,方案一只须要CSS便可,方案二须要js配合但灵活性更强。
加载中的动画不只能够用图片,还能够用CSS3的动画特性,对于以上两种方案稍做修改也能够适配CSS3的加载中动画提示:
方案一:加载动画做为外层div的内容,但z-index比图片低,等图片加载完便可覆盖;
方案二:用js控制img和加载中动画的显示与隐藏便可,图片加载完隐藏加载中动画,显示图片,并可设置过渡动画加强体验;
图片加载结束具备两种结果,要么加载成功,要么失败。加载成功,咱们不需多作处理,而加载失败会出现难看的裂图提示,影响体验。
本文介绍两种方法,替换图片加载失败的裂图提示。
img是可替换元素,即其表现的形式与内容是被外部资源控制。当img未加载时,属于它的:before与:after伪元素并未渲染,只有当图片加载失败时,这两个伪元素才会出现。
这里给出加载失败的样式例子,CSS关键样式以下:
.broken-image { width: 100%; position: relative; min-height: 50px; } .broken-image:before { content: " "; position: absolute; top: -10px; left: 0; height: calc(100% + 10px); width: 100%; background-color: rgb(230, 230, 230); border: 2px dotted rgb(200, 200, 200); border-radius: 5px; } .broken-image:after { content: "\f127" " Broken Image "; display: block; font-size: 16px; font-style: normal; font-family: FontAwesome; color: rgb(100, 100, 100); position: absolute; top: 5px; left: 0; width: 100%; text-align: center; line-height: 2; }
由于渲染后:before和:after伪元素位于img元素里,故将img元素设为relative,:before做为背景,绝对定位覆盖默认裂图提示,:after做为提示文字居中在img元素中。
<u>这种方法实现很方便,并且样式可控,可是兼容性实在太差</u>,只有Chrome支持,其余浏览器都没法在图片加载失败后渲染出:before和:after伪元素。
翻了翻规范 https://www.w3.org/TR/CSS22/g...
This specification does not fully define the interaction of :before and :after with replaced elements (such as IMG in HTML). This will be defined in more detail in a future specification.
因此,替换元素的:before、:after伪元素由浏览器产商本身实现,兼容性以下:
浏览器 | Chrome 54 | Safari 10.0 | Firefox 50.0 | IE 8 |
---|---|---|---|---|
兼容性 | √ | ✘ | ✘ | ✘ |
与加载成功事件load类似,<u>加载失败也会触发对应的事件——error</u>。咱们能够利用error事件,当加载失败时替换img的src为加载失败提示图的url。
关键代码以下:
Array.from(document.getElementsByTagName('img')).forEach(img => { img.onerror = () => { img.onerror = null; img.src = 'data:image/gif;base64...'; } });
其中<u>error响应事件中要记得将img的onerror属性置null</u>,不然一旦加载失败提示图也加载失败时会陷入死循环。
该方案能够支持当前主流浏览器:
浏览器 | Chrome 54 | Safari 10.0 | Firefox 50.0 | IE 8 |
---|---|---|---|---|
兼容性 | √ | √ | √ | √ |
以上两种替换图片加载失败裂图提示的方案,第一种因兼容性的关系只能用于特殊状况,第二种方案须要js配合,但无兼容性问题。