图像优化问题主要可以分为两方面:图像的选取和使用,图像的加载和显示。
图像基础
HTTP Archive上的数据显示,网站传输的数据中,60%的资源都是由各种图像文件组成的,当然这些是将各类型网站平均的结果,单独只看电商类网站,这个比例可能会更大,如此之大的资源占比,同样意味着有很大的优化空间。
图像是否必需
图像资源优化的根本思想:压缩。无论是选取何种图像的文件格式,还是针对于同一种格式压缩至更小的尺寸,其本质都是用更小的资源开销来完成图像的传输和展示。
我们首先需要思考下要达到期望的信息传递效果,是否需要图像?这不仅是因为图像资源与网页上的其他资源(html/css/js等)相比有更大的字节开销,出于对节省资源的考虑,对用户注意力的珍惜也很重要,如果一个页面打开之后有很多图像,那么用户其实很难快速梳理出有效的信息,即便获取到了也会让用户觉得很累。一个低感官体验的网站,它的价值转化率不会很高。
当确定了图像的展示效果必须存在时,在前端实现上也并非一定就要用图像文件,还存在一些场景可以使用更高效的方式来实现所需的效果。
- 网站中一个图像在不同的页面或不同的交互状态下,需要呈现不同的效果(边角的裁切、阴影或渐变),其实没有必要准备不同效果的图像,使用css即可,相对于一张图像文件的大小来讲,修改其所增加的css代码量忽略不计。
- 如果一个图像上面需要显示文字,建议通过网页字体的形式通过前端代码进行添加,而不是使用带文字的图像,其原因一方面是包含了更多信息的图像文件一般会更大,另一方面图像中的文本信息代码的用户体验一般较差(不可选择、搜索以及缩放),并且在高分辨率设备上的显示效果也会大打折扣。
因此,我们在选择使用某种资源之前,如果期望达到更优的性能效果,则需要先思考这种选择是否必须。
矢量图和位图
当确定了图像是实现展示效果的最佳方式时,接下来就是选择合适的图像格式。图像文件可以分为两类:矢量图和位图。
- 矢量图
矢量图的优点时能够在任何缩放比例下呈现出细节同样清晰的展示效果。其缺点是对细节的展示效果不够丰富,对足够复杂的图像来说,比如要达到照片的效果,若通过svg进行矢量图绘制,则所得文件会大的离谱,但即便如此也很难达到照片的真实效果。目前几乎所有的浏览器都支持svg。
svg标签所包括的部分就是该矢量图的全部内容,除了必要的绘制信息,可能还包括一些元数据,比如xml命名空间、图层以及注释信息。但这些信息对浏览器绘制一个svg来说不是必要的,所以在使用之前可通过工具去除这些元数据来达到压缩的目的。 - 位图
位图是通过对一个矩阵中的栅格进行编码来表示图像的,每个栅格只能编码表示一个特定的颜色,如果组成图像的栅格像素点越多且每个像素点所能表示的颜色范围越广,则位图图像的显示效果就会越逼真,当然图像文件也就越大。虽然位图没有像矢量图那种不受分辨率影响的优秀特性,但对于复杂的照片却能提供较为真实的细节体验。
分辨率
在前端开发过程中书写css时,经常会为图像设置显示所需的长宽像素值,但在不同的设备屏幕上,有时候相同的图像以及相同的设置,其渲染出来的图像会让人明显感觉出清晰度有差别。产生这个现象的原因涉及两种不同的分辨率:屏幕分辨率和图像分辨率。
图像分辨率展示的就是该图像文件所包含的真实像素值,比如一个200px200px的分辨率的图像文件,它就定义了长宽各200各像素点的信息。设备分辨率则是显示器屏幕所能显示的最大像素值,比如一台电脑的显示器分辨率为2560px1600px。更高的设备分辨率有助于显示更绚丽多彩的图像,这其实很适合矢量图的发挥,因为其不会失真。而对于位图来说,只有图像文件包含更多的像素信息时,才能更充分地利用屏幕分辨率。为了能在不同的分辨率下使项目中所包含的图像都能得到恰当的展示效果。可以利用picture标签和srcset属性提供图像的多个变体。
<img src='photo.jpg' srcset='photo@2x.jpg,photo@3x.jpg' alt='photo'/>
除了ie和其他较低版本的浏览器不支持,目前主流的大部分浏览器都已支持img标签的srcset属性。在srcset属性中设置多种分辨率的图像文件以及使用条件,浏览器在请求之前便会先对此进行解析,只选择最合适的图像文件进行下载,如果浏览器不支持,务必在src属性中包含必要的默认图片。
使用picture标签则会在多图像文件选择时,获得更多的控制维度,比如屏幕方向、设备大小、屏幕分辨率等。
<picture>
<source media="(min-width:800px)" srcset="photo.jpg,photo@2x.jpg"/>
<source media="(min-width:450px)" srcset="photo.jpg,photo@2x.jpg"/>
<img src='photo.jpg'/>
</picture>
由于picture标签也是加入标准不久的元素标签,所以在使用过程中,同样应考虑兼容问题。
无损和有损压缩
压缩时降低源文件大小的有效方式,对js代码或网页的一些脚本文件而言,压缩掉的内容是一些多余的空格以及不影响执行的注释,其目的是在不损坏正常执行的情况下,尽量缩小源文件的大小。对图像文件而言,由于人眼对不同颜色的敏感度存在差异,所以便可通过减少对某种颜色的编码位数来减少文件大小,甚至还可以损失部分源文件信息,以达到近似效果,使得压缩后的文件尺寸更小。
对于图像压缩,应该是使用有损压缩还是无损压缩,可以简单分为两步进行。
- 确定业务所需要展示图像的颜色阶数、图像显示的分辨率以及清晰程度,当锚定了这几个参数的基准后,如果获取的图像源文件的相应参数指标过高,便可适当进行有损压缩,通过降低源文件图像质量的方法来降低图像文件大小。
如果业务所要求的图像质量较高,便可跳过有损压缩,直接进入第二步无损压缩。所以是否要进行有损压缩,其实是在理解了业务需求后的一个可选选项,而非必要的。 - 当确定了展示图像的质量后,便可利用无损压缩技术尽可能降低图像大小。无损压缩是应当完成的工作环节。因此最好能通过一套完整的工程方案,自动化执行来避免繁琐的人工重复工作。
CSS Sprite
雪碧图,通过将多张小图标拼接成一张大图,有效地减少http请求数量以达到加速显示内容的技术。
通常对于雪碧图的使用场景应当满足以下条件:首先这些图标不会随用户信息的变化而变化,它们属于网站通用的静态图标;同时单张图标体积尽量小,这样经过拼接后其性能的提升才会比较乐观;若加载量比较大则效果会更好。但是不建议将较大的图片拼接成雪碧图,因为大图拼接后的单个文件体积会非常大,这样占用网络带宽的增加与请求完成所耗费时间的延长,会完全淹没通过减少http请求次数所带来的性能提升。
雪碧图的使用方式十分简单:通过css的background-image属性引入雪碧图的url后,再使用background-position定位所需要的单个图标再雪碧图上的起始位置,配合width和height属性来锁定具体图标的尺寸。
.sprite-sheet{background-image:url(https://xxxxx);background-size: 24px 600px
}.icon-1 .sprite-sheet{background-position: 0 0;height:24px;width:24px;
}.icon-2 .sprite-sheet{background-position: 0 -24px;height:24px;width:24px;
}
使用雪碧图来提升小图标加载性能的历史由来已久。在http1.x环境下,它确实能够减少相应的http请求,但需要注意当部分图标变更时,会导致已经加载的雪碧图缓存失效。同时在http2中,最好的方式应该是加载单张图像文件,因为可以在一个http连接上发起多次请求,所以对于是否使用此方法,需要考虑具体的使用环境和网络设置。
图像格式选择建议
首先明确告诉读者:不存在适用于任何场景且性能最优的图像使用方式。所以作为开发者,想要网站性能在图像方面达到最优,如何根据业务场景选择合适的文件格式尤其重要,图像文件的使用策略如下图所示:
注意:使用webp格式的图像需要考虑到浏览器的兼容性。通常的处理思路分为两种:一种是在前端处理浏览器兼容性的判断,可以通过浏览器的全局属性window.navigator.userAgent获取版本信息,再根据兼容支持情况,选择是否请求webp图像格式的资源;也可以使用<picture/>标签来选择显示的图像格式。
<picture><source srcset="photo.webp" type="image/webp"/><img src='photo.jpg'/>
</picture>
除此之外,位图对于不同缩放比的响应式场景,建议提供多张不同尺寸的图像,让浏览器根据具体场景进行请求调用。
参考文章:https://blog.csdn.net/qq_42691298/article/details/128485051
Web字体
使用web字体有多种优点:增强网站的设计感、可读性,同时还能搜索和选取所表示的文本内容,且不受屏幕尺寸与分辨率的影响,能提供一致的视觉体验。除此之外,由于每个字型都是特定的矢量图标,所以可以将项目中用到的矢量图打包到一个web字体文件中使用,以节省对图标资源的http请求次数,这样做类似雪碧图优化目的。
-
字体的使用
目前网络上常用的字体格式有:EOT、TTF、WOFF与WOFF2,由于存在兼容问题,并没有哪一种字体能够适用所有浏览器,所以在实际使用中,网站开发者会声明提供字体的多种文件格式,来达到一致性的体验效果。
在web项目中,一般会先通过@font-face声明使用的字体系列:@font-face {font-family: 'myFont';src: url('./my-font.ttf') format('truetype');src: url('./my-font.woff') format('woff'),url('./my-font.woff2') format('woff2');font-weight: 600;font-style: normal; }.my-font {font-family: 'myFont';color: red; }
在上述代码中通过src字段的属性值,可以指定字体资源的位置,并且该属性值还可以提供一个用逗号分隔的列表,列表中不同字体文件格式的资源顺序同样重要,浏览器将选取其所支持的第一个格式资源。如果希望较新的woff2格式被使用,则应当将woff2声明在woff之上。
-
子集内嵌
如果将所有字型都打包成一个文件来请求使用,不免就会存在许多根本用不到的字型信息浪费带宽。相较于拉丁文字体而言,包含中文字符的字体文件的大小会格外突出。可以使用unicode-range属性定义所使用的字体子集。它支持三种形式:单一取值(如U+233)、范围取值(如U+233-2ff)、通配符范围(如U+2??),取值的含义是字体集文件中的代码索引点:@font-face {font-family: 'myFont';src: url('./my-font.ttf') format('truetype');src: url('./my-font.woff') format('woff'),url('./my-font.woff2') format('woff2');unicode-range:U+100-3ff,U+f??;font-weight: 600;font-style: normal; }
通过使用子集内嵌,以及为字体的不同样式变体采用单独的文件,用户可以仅根据需要下载字体的子集,而不必强制他们下载可能永远都不会用到的字体子集。不过属性unicode-range也存在兼容问题,对于不支持的浏览器,可能需要手动处理字体文件。
-
字体文件预加载
在默认情况下,构建渲染树之前会阻塞字体文件的请求,这将可能导致部分文本渲染延迟,对此可使用<link rel=“preload”>对字体资源进行加载。
<head>
<link rel="preload" href="xxxx" as ="font"/>
</head>
link需要和@font-face对字体的定义一同使用,它只负责提示浏览器需要预加载给定的资源,而不指明如何使用。需要注意的是,这样做将会无条件向网络发出字体请求如果项目迭代将原本使用的字体文件修改或删除,也需要同步删除对字体预加载的设置。
参考文章:https://blog.csdn.net/weixin_46820017/article/details/116666903
注意display:none的使用
在使用位图时,经常会根据屏幕尺寸,权限控制等不同条件,响应式地处理资源的展示与隐藏。出于对性能的考虑,希望对于不展示的图像,尽量避免在首屏时进行资源请求加载。
<div style="display:none"><img src="img.png"/>
</div>
根据html的解析顺序,img.png的图像文件会被请求。
<div style="display:none"><div style="background:url(img.png)"/>
</div>
css解析后发现父级使用了none,再去计算子级的样式就没有多大意义了,所以就不会去下载子级div的背景图片。
如果不清楚不同浏览器对display:none关于图像加载的控制,则可以通过开发者工具进行验证。
笔者这里推荐的做法是使用<picture/>或<img srcset>的方式进行响应式显示。
图像延迟加载
在首次打开网站时,应尽量只加载首屏内容所包含的资源,而首屏之外涉及的图片或视频,可以等到用户滚动视窗浏览时再去加载。
实现图片的延迟加载:传统方式
通过监听的方式,通过监听scroll事件与resize事件,并在事件的回调函数中去判断,需要进行延迟加载的图片是否进入视窗区域。
首先定义出将要实现延迟加载的<img>标签结构:
<img class="lazy" alt="" src="xxxx" data-src="xxxx"/>
- src属性,加载前的占位符图片,可用base64图片或低分辨率的图片。
- data-src属性,通过该自定义属性保存图片真实的url外链。
对于只可上下滚动的页面,判断一个图片元素是否出现在屏幕视窗中的方法其实显而易见,即当元素上边缘距屏幕视窗顶部的top值小于整个视窗的高度window.innerHeight时,预加载的事件处理代码如下:
//在dom内容加载完毕后,执行延迟加载处理逻辑
document.addEventListener("DOMContentLoaded",function(){//获取所有需要延迟加载的图片let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));//限制函数频繁被调用let active= false;const lazyLoad = function(){if(active==false){active = true;setTimeout(function(){lazyImages.forEach(function(lazyImage){//判断图片是否出现在视窗中if((lazyImage.getBoundingClientRect().top<=window.innerHeight&&lazyImage.getBoundingClientRect().bottom>=0)&&getComputedStyle(lazyImage).display!=='none'){//将真实的图片url赋值给src属性,发起请求加载资源lazyImage.src = lazyImage.dataset.src;lazyImage.classList.remove("lazy");lazyImages = lazyImages.filter(function(image){return image !== lazyImage;})//所有延迟加载图片加载完成后,移除事件触发处理函数if(lazyImages.length===0){document.removeEventListener("scroll",lazyLoad);document.removeEventListener("resize",lazyLoad);document.removeEventListener("orientationchange",lazyLoad);}}});active=false;},200)}};document.addEventListener("scroll",lazyLoad);document.addEventListener("resize",lazyLoad);document.addEventListener("orientationchange",lazyLoad);
})
由于无法控制用户随心所欲地滑动鼠标滚轮,从而造成scroll事件被触发地过于频繁,导致过多的冗余计算影响性能。所以通过active标志位的方式进行限流。
即便如此也有潜在的性能问题,因为重复的setTimeout调用时浪费的,虽然进行了触发限制,但当文档滚动或窗口大小调整时,不论图片是否出现在视窗中,每200ms都会执行一次检查,并且跟踪尚未加载的图片数量,以及完全加载完后,取消绑定滚动事件的处理函数等操作都需要开发者来考虑。
如此看来,虽然传统的延迟加载方式具有良好的浏览器兼容性,但是实现起来比较琐碎。
实现图片的延迟加载:IntersectionObserver方式
现代浏览器大多支持了IntersectionObserver API,可以通过它来检查目标元素的可见性,这种方式的性能和效率都比较好。
IntersectionObserver:每当因页面滚动或窗口尺寸发生变化,使得目标元素与设备视窗或其他指定元素产生交集时,便会触发通过IntersectionObserver API配置的回调函数,在该函数中进行延迟加载的逻辑处理,会比传统方式显得更加简洁而高效。
//在dom内容加载完毕后,执行延迟加载处理逻辑
document.addEventListener("DOMContentLoaded",function(){//获取所有需要延迟加载的图片let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));//判断浏览器兼容性if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype){let lazyImageObserver = new IntersectionObserver(function (entries,observer){entries.forEach(function(entry){//判断图片是否出现在视窗中if(entry.isIntersecting){let lazyImage = entry.target;lazyImage.src = lazyImage.dataset.src;lazyImage.classList.remove("lazy");lazyImageObserver.unobserver(lazyImage);}})})lazyImages.forEach(function(lazyImage){lazyImageObserver.observer(lazyImage)})}
})
这种方式判断元素是否出现在视窗中更为简单直观,应在实际开发中尽量使用。但其问题是并非所有浏览器都能兼容。在将这种方式引入项目之前,应当确保已做到以下两点:
- 做好尽量完备浏览器兼容性检查,对于兼容IntersectionObserver API的浏览器,采取这种方式处理,而对于不兼容的浏览器,则使用传统的实现方式。
- 使用相应兼容的polyfill插件。
实现图片的延迟加载:CSS类名方式
这种方式通过css的background-image属性来加载图片,与判断<img>标签的src属性是否有要请求图片的url不同,css中图片加载的行为建立在浏览器对文档分析基础之上。
具体来说,当dom树,cssom树以及渲染树生成后,浏览器会去检查css以何种方式应用于文档,再决定是否请求外部资源。如果浏览器确定涉及外部资源请求的css规则再当前文档中不存在,便不会去请求该资源。
<div class="wrap"><div class="lazy-background one"/><div class="lazy-background two"/><div class="lazy-background three"/>
</div>
具体的实现方式是通过js来判断元素是否出现在视窗中,当在视窗中时,为其元素的class属性添加visible类名。而在css文件中,为同一类名元素定义出带.visible和不带.visible的两种包含background-image规则。
不带.visible的图片规则中的background-image属性可以是低分辨率的图片或base64图片,而带.visible的图片规则中的background-image属性为希望展示的真实图片的url。
//在dom内容加载完毕后,执行延迟加载处理逻辑
document.addEventListener("DOMContentLoaded",function(){//获取所有需要延迟加载的图片let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));//判断浏览器兼容性if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype){let lazyImageObserver = new IntersectionObserver(function (entries,observer){entries.forEach(function(entry){//判断图片是否出现在视窗中if(entry.isIntersecting){entry.target.classList.add("visible")lazyImageObserver.unobserver(entry.target);}})})lazyImages.forEach(function(lazyImage){lazyImageObserver.observer(lazyImage)})}
})
原生的延迟加载支持
除了上述通过开发者手动实现延迟加载逻辑的方式,从chrome75版本开始,已经可以通过<img>和<iframe>标签的loading属性原生支持延迟加载了,loading属性包含了以下三种取值。
- lazy:进行延迟加载
- eager:立即加载
- auto:浏览器自行决定是否进行延迟加载(默认值)
兼容性处理:通过使用新技术优化了延迟加载的实现方式,同时也应该注意新技术在不同浏览器之间的兼容性,在使用前需要对浏览器特性进行检查。
<script>
if("loading" in HTMLImageElement.prototype){//浏览器支持loading的延迟加载方式
}else{//获取其他js库来实现延迟加载
}
</script>
视频加载
不需要自动播放
由于chrome等一些浏览器会对视频资源进行预加载,即在html完成加载和解析时触发DOMContentLoaded事件开始请求视频资源,当请求完成后触发window.onload事件开始页面渲染。
为了使页面更快地加载并渲染出来,可以阻止不需要自动播放的视频的预加载。其方法是通过视频标签的preload进行控制:
<video preload="none" poster="default.png"><source src="simply.webm" type="video/webm" /><source src="simply.mp4" type="video/mp4" />
</video>
preload属性通常的默认值是auto,表示无论用户是否希望,所有视频文件都会被自动下载,这里将其设置为none,来阻止视频的自动预加载。同时还通过poster属性为视频提供占位符图片。
- chrome之前的版本中,preload的默认值是auto,从64版本以后其默认值改为了metadata,表示仅加载视频的元数据,火狐、ie11和edge等浏览器的行为类似。
- safari11.0的mac版本会默认进行部分视频资源预加载,11.2的mac版后仅可预加载元数据,但ios的safari不会对视频预加载。
- 若浏览器开启了省流量模式,preload将默认值设置为none。
当浏览器支持preload的metadata属性值后,这将会是一种兼顾了性能和体验后更优的方式,因为从体验上说,对于不自动播放的视频场景,在单击播放前,若能提前告知视频的播放时长、播放列表等元数据,便能带来更好的用户体验。
另外,如果站点中包含了同一域名下的多个视频资源,那么最好将preload设置为metadata,或者定义poster属性值时将preload设置为none,这样可以很好地避免http地最大连接数,因为通常http1.1协议规定同一域名下地最大连接数为6,如果同时有超过此数量地资源请求连接,那么多余地连接便会被挂起,这无疑会对性能造成负面影响。
视频代替GIF动画
应当尽量用视频代替尺寸过大地gif动画。虽然gif动画的应用历史和范围都很广泛,但其在输出文件大小、图像色彩质量等许多方面的表现都不如视频。gif动画相对于视频具有三个附加的特性:没有音轨、连续循环播放、加载完自动播放,替换成视频后类似于:
<video autoplay muted loop playsinline width="610" height="254" poster="default.png"><source src="simply.webm" type="video/webm" /><source src="simply.mp4" type="video/mp4" />
</video>
autoplay自动播放、muted静音播放以及loop循环播放,而playsinline属性则用于在ios中指定自动播放的。注意并非所有浏览器都像chrome一样,能够自动进行延迟加载,因此我们可以根据上面的提及到的IntersectionObserver的方式实现延迟加载的控制。
//在dom内容加载完毕后,执行延迟加载处理逻辑
document.addEventListener("DOMContentLoaded",function(){//获取所有需要延迟加载的图片let lazyImages = [].slice.call(document.querySelectorAll("video.lazy"));//判断浏览器兼容性if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype){let lazyImageObserver = new IntersectionObserver(function (entries,observer){entries.forEach(function(entry){//判断图片是否出现在视窗中if(entry.isIntersecting){for(const source in entry.target,children){const videoSrc = entry.target.children[source];if(typeof videoSrc.tagName==='string' && videoSrc.tagName==='source'){videoSrc.src = videoSrc.dataset.src;}}entry.target.load();//需要手动调用load方法entry.target.classList.remove("lazy")lazyImageObserver.unobserver(entry.target);}})})lazyImages.forEach(function(lazyImage){lazyImageObserver.observer(lazyImage)})}
})
加载注意事项
首屏加载
对于首屏上的内容就不应当进行延迟加载,而应该正常加载。这样处理的原因是:延迟加载会将图像或视频等媒体资源延迟到dom可交互之后,即脚本完成加载并开始执行时才会进行。所以对首屏视窗之外的媒体资源采用延迟加载,而对首屏视窗内的媒体资源采用正常的方式加载,会带来更好的性能体验。
由于网站页面所呈现的设备屏幕尺寸多种多样,因此如何判断首屏视窗的边界就会因设备的不同而有所不同。目前也没有完全行之有效的方法来完美地处理每种设备的情况。
此外,若将首屏视窗边界线作为延迟加载触发的阈值,其实并非最佳的性能考虑。更为理想的做法是,在延迟加载的媒体资源到达首屏边界之前设置一个缓冲区,以便媒体资源在进入视窗之前就开始进行加载。
let lazyImageObserver = new IntersectionObserver(function (entries,observer){
},{rootMargin:"0 0 256px 0"})//当媒体元素距离视窗下边界小于256px时,回调函数会执行
内容加载失败
const newImage = new Image();
newImage.src="photo.jpg"
//当发生故障时的处理措施
newImage.onerror=(err)=>{
}
//图像加载后的回调
newImage.onload=()=>{
}
当图片资源未能按预期成功加载时,所采取的具体处理措施应当依据应用场景而定。比如,当请求的媒体资源无法加载时,可将使用的图像占位符替换成按钮,或者在占位符区域显示错误的提示信息。
图像解码延迟
图像从被浏览器请求获取,再到最终完整呈现在屏幕上,需要经历一个解码的过程,图像的尺寸越大,所需要的解码时间就越长。如果在js中请求加载较大的图像文件,并把它直接放入dom结构中后,那么将可能占用浏览器的主进程,进而导致解码期间用户界面出现短暂的无响应。
<button id="load-image">加载图像
</button>
<div id="image-container"/>
对于的js事件处理代码如下:
document.addEventListener("DOMCOntentLoaded",()=>{const loadButton = document.getElementById("load-image");const imageContainer = document.getElementById("image-container");const newImage = new Image();newImage.src = "https://sssss"loadButton.addEventListener("click",function(){if("decode" in newImage){//异步解码方式newImage.decode().then(()=>{imageContainer.appendChild(newImage);})}else{//正常图像加载方式imageContainer.appendChild(newImage);}},{once:true})
})
需要说明的是,如果网站所包含的大部分图像尺寸都很小,那么使用这种方式的帮助并不大,同时还会增加代码的复杂性,但可以肯定的是这么做会减少延迟加载大型图像文件所带来的卡顿。
js是否可用
在通常情况下,js都是始终可用的,但在一些异常不可用的情况下,开发者应当做好适配,不能始终在延迟加载的图像位置上展示占位符。可以考虑使用<noscript>标记,在js不可用时提供图像的真实展示:
<!-- 使用延迟加载的图像文件标签 -->
<img class = "lazy" src="aaa.png" data-src = "load.png" alt="xxx"/>
<!-- js不可用时,原生展示目标图像 -->
<noscript><img src="load.png" alt="xxxx"/>
</noscript>
如果上述代码同时存在,当js不可用时,页面中会同时展示图像占位符和<noscript>中包含的图像,为此可以给<html>标签添加一个no-js类:
<html class="no-js">
在由<link>标签请求css文件之前,在<head>标签结构中放置一段内联脚本,当js可用时,用于移除no-js类:
<script>document.documentElement.classList.remove("no-js")
</script>
以及添加必要的css样式,使得在js不可用时屏蔽包含.lazy类元素的显示:
.no-js .lazy{display:none;
}
资源优先级
浏览器向网络请求到所有数据,并非每个字节都具有相同的优先级或重要性。所以浏览器通常都会采取启发式算法,对所要加载的内容先进行推测,将相对重要的信息优先呈现给用户。比如浏览器一般会先加载css文件,然后再去加载js脚本和图像文件。
但即便如此,也无法保证启发式算法在任何情况下都是准确有效的。
预加载
使用<link rel=“preload”>标签告诉浏览器当前所指定的资源,应该拥有更高的优先级,例如:
<link rel="preload" as="script" href="test.js"/><link rel="preload" as="style" href="test.css"/><!-- 通常字体文件都位于页面加载的若干css文件的末尾,当为了减少用户等待文本内容的时间,就必须要提取获取字体 --><link rel="preload" as="font" type="font/woff2" crossorigin="crossorigin" href="test.woff2"/>
这里通过as属性告诉浏览器所要加载的资源类型,该属性值所指定的资源类型应该与要加载的资源相匹配,否则浏览器是不会预加载该资源的。在这里需要注意的是,<link rel=“preload”>会强制浏览器进行预加载,它与其他对资源的提示不同,浏览器对此是必须执行的。因此,在使用时应该尽量仔细测试,以确保使用该指令时不会提取不需要的内容或重复的内容。
如果预加载指定的资源在3s内未被当前页面使用,则浏览器会在开发者工具的控制台中进行告警提示,该告警提示务必要处理。
预连接
通常在速度较慢的网络环境中建立连接会非常耗时,如果建立安全连接将更加耗时。其原因是整个过程会涉及到dns查询,重定向和与目标服务器之间建立连接的多次握手,所以若能提前完成上述这些功能,则会给用户带来更加流畅的浏览体验,同时由于建立连接的大部分时间消耗是等待而非交换数据,这样也能有效地优化带宽的使用情况。
<link rel="preconnect" href="https://www.xxxx"/>
通过该指令,告诉浏览器当前页面将与站点建立连接,希望尽快启动该过程。虽然这么做的成本较低,但会消耗宝贵的cpu时间,特别是在建立https安全连接时。如果建立好连接后的10s内,未能及时使用连接,那么浏览器关闭该连接后,之前未建立连接所消耗的资源相当于完全浪费掉了。
另外,还有一种与预连接相关的类型<link rel=“dns-prefetch”>,也就是常说的dns预解析,它仅用来处理dns查询,但由于其受到浏览器的广泛支持,且缩短了dns的查询时间的效果显著,所以使用场景十分普遍。
预提取
前面介绍的预加载和预连接,都是试图使所需的关键资源或关键操作更快地获取或发生,这里介绍地预提取,则是利用机会让某些非关键操作能够更早发生。
这个过程的实现方式是根据用户已经发生的行为来判断其接下来的预期行为,告诉浏览器稍后可能需要的某些资源。也就是在当前页面加载完成后,且在宽带可用的情况下,这些资源将以Lowest的优先级进行提起。
显而易见,预提取最合适的场景是为用户下一步可能进行的操作做好必要的准备,如在电商平台的搜索框中查询商品,可预提取查询结果列表中的首个商品详情页;或者使用搜索查询时,预提取查询结果的分页内容的下一页:
<link rel="prefetch" href="page-2.html"/>
需要注意的是,预提取不能递归使用,比如在搜索查询的首页page-1.html时,可以预提取当前页面的下一页page-2.html的html内容,但对其中所包含的任何额外资源不会提前下载,除非有额外明确指定的预提取。
另外,预提取不会降低现有资源的优先级,比如在如下html中:
<html><head><link rel="prefetch" href="style.css"/><link rel="stylesheet" href="style.css"/> </head>
</html>
可能读者会觉得对style.css的预提取声明,会降低接下来<link rel=“stylesheet”>的优先级,当其实的情况是,该文件会被提取两次,第二次可能会使用缓存。显然两次提取对用户体验来说时非常糟糕的,因为这样不但需要等待阻塞渲染的css,而且如果第二次提取没有命中缓存,必然会产生带宽的浪费,所以在使用时应充分考虑。