web前端性能优化——图片加载的优化

怀抱观古今,寝食展戏谑。这篇文章主要讲述web前端性能优化——图片加载的优化相关的知识,希望能为你提供帮助。
图像延迟加载想要得到更好的性能体验,只靠资源压缩与恰当的文件格式选型,是很难满足期望的。我们还需要针对资源加载过程进行优化。
什么是延迟加载?
下图是京东商城的手机端首页,当元素没有滑动到视线内时,图片src属性放置了一个很小的图片,init_src属性放置了真正的图片,只要当该元素滑动到视线内部,才会将init_src属性赋值给src去加载真实的图片,这就是一个简单的图片延迟加载的过程。

web前端性能优化——图片加载的优化

文章图片

web前端性能优化——图片加载的优化

文章图片

传统方式延迟加载
就是事件监听的方式,通过监听scroll事件与resize事件,并在事件的回调函数中去判断,需要进行延迟加载的图片是否进入视窗区域。
我们只需要关注三个属性。
  • class属性,稍后会在javascript中使用类选择器选取需要延迟加载处理的〈img〉标签。
  • src属性,加载前的占位符图片,可用Base64图片或低分辨率的图片。
  • data-src属性,通过该自定义属性保存图片真实的URL外链。
web前端性能优化——图片加载的优化

文章图片

加入页面中有多张这样的图片需要加载。具体的javaScript实现逻辑如下,在文档的DOMContentLoaded事件中,添加延迟加载处理逻辑,首先获取class属性名为lazy的所有〈img〉标签,将这些标签暂存在一个名为lazyImages的数组中,表示需要进行延迟加载但还未加载的图片集合。当一个图片被加载后,便将其从lazyImages数组中移除,直到lazyImages数组为空时,表示所有待延迟加载的图片均已经加载完成,此时便可将页面滚动事件移除。
这里使用了getBoundingClientRect()函数获取元素的相对位置.
rectObject = object.getBoundingClientRect();

rectObject.top:元素上边到视窗上边的距离;
rectObject.right:元素右边到视窗左边的距离;
rectObject.bottom:元素下边到视窗上边的距离;
rectObject.left:元素左边到视窗左边的距离;
web前端性能优化——图片加载的优化

文章图片

对于只可上下滚动的页面,判断一个图片元素是否出现在屏幕视窗中的方法其实显而易见,即当元素上边缘距屏幕视窗顶部的top值小于整个视窗的高度window.innerHeight时,预加载的事件处理代码如下:
document.addEventListener(DOMContentLoaded, function() { const imags = [].slice.call(document.querySelector(\'.lazy\')) const active = false; // 限制函数被频繁调动 function load() { if(active === false) { active = true setTimeout(() => { imags.forEach((img) => { const objPos = img.getBoundingClientRect(); if(objPos.top < = window.innerHeight & & objPos.bottom > =0 & & img.display !== \'done\') { img.src = https://www.songbingjia.com/android/img.dataset.src; img.classList.remove(/'lazy\') imags.filter((i) => (i !== img)) if(imags.length === 0) { document.removeEventListener(\'scroll\', load) window.removeEventListener(\'resize\', load) window.removeEventListener(\'orientationchange\', load) }} }) active = false }, 200) }}document.addEventListener(\'scroll\', load) window.addEventListener(\'resize\', load) window.addEventListener(\'orientationchange\', load)})

这种方式的有点就是兼容性比较号,缺点是频繁的进行计算必然会影响性能,代码也会比较繁琐。
实现图片的延迟加载:Intersection Observer方式
现代浏览器已大多支持了Intersection Observer API,用一句话简述:每当因页面滚动或窗口尺寸发生变化,使得目标元素(target)与设备视窗或其他指定元素产生交集时,便会触发通过Intersection Observer API配置的回调函数,在该回调函数中进行延迟加载的逻辑处理,会比传统方式显得更加简洁而高效。
简单来说,目标元素的可见性变化时,就会调用观察器的回调函数 callback
callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
document.addEventListener(DOMContentLoaded, function() { const imags = [].slice.call(document.querySelector(\'.lazy\')) if(window.IntersectionObserver & & window.IntersectionObserverEntry & & window.IntersectionObserverEntry.prototype.intersectionRatio) { var lazyImgObserver = new IntersectionObserver((entries, observer) => { entries.forEach((entry)=> { if(entry.isIntersecting) { var lazyImg = entry.target; lazyImg.src = https://www.songbingjia.com/android/lazyImg.dataset.src; lazyImg.classList.remove(/'lazy\'); lazyImgObserver.unobserve(lazyImg) } }) }) imags.forEach((img) => { lazyImgObserver.observe(img) }) }})

这种方式判断元素是否出现在视窗中更为简单直观,应在实际开发中尽量使用,但其问题是并非所有浏览器都能兼容。
(1)做好尽量完备浏览器兼容性检查,对于兼容Intersection Observer API的浏览器,采用这种方式进行处理,而对于不兼容的浏览器,则切换回传统的实现方式进行处理。
(2)使用相应兼容的polyfill插件,在W3C官方GitHub账号下就有提供。
实现图片的延迟加载:CSS类名方式
这种实现方式通过CSS的background-image属性来加载图片,与判断〈img〉标签src属性是否有要请求图片的URL不同,CSS中图片加载的行为建立在浏览器对文档分析基础之上。
document.addEventListener(DOMContentLoaded, function() { const imags = [].slice.call(document.querySelector(\'.lazy\')) if(window.IntersectionObserver & & window.IntersectionObserverEntry & & window.IntersectionObserverEntry.prototype.intersectionRatio) { var lazyImgObserver = new IntersectionObserver((entries, observer) => { entries.forEach((entry)=> { if(entry.isIntersecting) { var lazyImg = entry.target; lazyImg.classList.add(\'visible\'); lazyImgObserver.unobserve(lazyImg) } }) }) imags.forEach((img) => { lazyImgObserver.observe(img) }) }})

web前端性能优化——图片加载的优化

文章图片

这种方式限制于需要提前写好css样式。
原生的延迟加载支持
除了上述通过开发者手动实现延迟加载逻辑的方式,从Chrome 75版本开始,已经可以通过〈img〉和〈iframe〉标签的loading属性原生支持延迟加载了,loading属性包含以下三种取值。
【web前端性能优化——图片加载的优化】● lazy:进行延迟加载。
● eager:立即加载。
● auto:浏览器自行决定是否进行延迟加载。
测试:image标签就是 img
< !DOCTYPE html> < html lang="en"> < head> < meta charset="UTF-8"> < meta http-equiv="X-UA-Compatible" content="IE=edge"> < meta name="viewport" content="width=device-width, initial-scale=1.0"> < title> Document< /title> < !-- < script src="https://www.songbingjia.com/android/js/file2.js"> < /script> --> < !-- < script src="https://www.songbingjia.com/android/js/file3.js"> < /script> --> < !-- < link rel="stylesheet" href="https://www.songbingjia.com/android/css/index.css"> --> < style> img { width: 700px; height: 200px; display: block; } < /style> < /head> < body> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-1.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-2.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-3.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-4.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-5.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-6.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-7.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-8.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-9.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-10.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-11.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-12.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-13.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-14.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-15.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-16.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-17.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-18.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-19.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-20.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-21.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-22.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-23.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-24.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-25.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-26.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-27.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-28.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-29.png\'/> < imgage loading="lazy" src=https://www.songbingjia.com/'./image/home-30.png\'/> < /body> < /html>

web前端性能优化——图片加载的优化

文章图片

可以看到,首次加载的个数是13个,首屏一般只能放下4个左右,13个以后的img滚动到视线内部会自动去加载。
实践发现有一下几个特点:
  1. Lazy loading加载数量与屏幕高度有关,高度越小加载数量越少,但并不是线性关系。
  2. Lazy loading加载数量与网速有关,网速越慢,加载数量越多,但并不是线性关系。
  3. Lazy loading加载没有缓冲,滚动即会触发新的图片资源加载。
  4. Lazy loading加载在窗口resize尺寸变化时候也会触发,例如屏幕高度从小变大的时候。
  5. Lazy loading加载也有可能会先加载后面的图片资源,例如页面加载时滚动高度很高的时候。
与JavaScript有关的几个行为特征:
  1. 判断浏览器是否支持原生loading,最好使用\'loading\' in XXX判断。
  2. 获取loading属性值可以直接img.loading;
  3. 原生loading不可写,不可访问例如HTMLImageElement.prototype.loading会报错Illegal invocation。
  4. 如果要使用,注意做兼容性处理。

    推荐阅读