富媒体资源元素
- 1、深入图像元素
- 1.1、了解`<svg>`元素
- 1.1.1、命名空间
- 1.1.2、viewBox视区盒子
- 1.1.3、preserveAspectRatio属性与缩放规则
- 1.1.4、SVG文件的内联与外链显示
- 1.2、`<canvas>`元素的基本特性
- 1.2.1、尺寸
- 1.2.2、坐标系
- 1.2.3、图像转换
- 1.3、专为图像显示设计的`<figure>`元素和`<figcaption>`元素
- 1.4、srcset、sizes等属性与响应式图片开发
- 1.4.1、先从基础的srcset属性说起
- 1.4.2、sizes属性和w描述符的使用场景
- 1.5、实用却不常用的`<picture>`元素
- 1.5.1、不同尺寸显示不同图片
- 1.5.2、不同宽度、不同屏幕密度显示不同图片
- 1.5.3、不同浏览器显示不同后缀图片
- 1.6、聊聊loading="lazy"(懒加载)
- 1.6.1、懒加载的策略
- 1.6.2、语法
- 1.7、crossOrigin与跨域处理
- 1.7.1、语法
- 1.7.2、其他跨域处理方法
- 1.8、使用elementtiming属性知晓图片的加载时长
在前端开发中,富媒体开发是有专门的前端岗位的,主要负责图像、音频和视频的处理,从表层到底层,所需要的技术积累跨度特别大。以视频为例,对于表层应用,我们简简单单使用一个<video>元素就可以了;对于深层应用,如要对视频进行特效合成,就需要了解视频的编解码、图像特效处理等,那不仅需要精通JavaScript,还需要众多其他富媒体相关的知识积累,并非易事。
相对地,有门槛也就意味着有竞争力,所以本章的内容还是很有价值的。富媒体开发一直是我专注且擅长的领域之一,相信本章内容一定会让你受益颇丰。
下面先从大家都一定使用过的图像元素说起。
1、深入图像元素
Web开发中常用的图像元素有三个:<svg>、<canvas>和<img>。
其中,<svg>元素因为其矢量特性,适合色彩简单的图形;<canvas>元素是位图,需要JavaScript代码参与绘制才能实现图像效果,多用来实现特效或图像处理,例如HTML转图像、图片前端压缩等;<img>元素则用来显示头像、素材图等常规图片。
这三个图像元素都非常重要,各有各的使用场景,涉及的知识也非常多,且听我娓娓道来。
1.1、了解<svg>元素
按照渲染方式的不同,图像可以分为两类:一类是位图,另一类是矢量图。位图的所有图形都是固定的像素点,面积越大,体积越大。矢量图的图形本质上是数学计算的结果,是根据坐标和形状指令实时绘制的图形,文件体积的大小和最终呈现的图像大小无关。SVG图形就是典型的矢量图,SVG的全称是Scalable Vector Graphics,即可缩放矢量图形。
不仅形状,就连矢量图的颜色也都是经过数学计算得到的,这就注定矢量图的颜色只能以纯色或者渐变色为主,所以SVG图形非常适合实现各类小图标效果。
SVG是个独立的语言体系,包含大量特有的元素,例如<path>、<circle>、<polyline>、<image>等,值得用单独一本书来讲述。本书并没有详细介绍这些元素的打算,只会从实用的角度出发,简单介绍SVG图形的使用注意事项。
在SVG图形的常规使用中,只要出现渲染问题,无外乎下面两个原因:
<svg>元素的命名空间没有设置。<svg>元素的viewBox视区异常。
1.1.1、命名空间
命名空间是Web开发中一个比较重要的概念,存在于任意的XML文档类型中,例如在HTML 5普及之前,XHTML文档的头部都会有这样的命名空间设置:
用来和传统的HTML 4文档进行区分,在部分CSS样式的渲染细节上会有差异。
为何需要命名空间呢?除影响渲染规则以外,还有一个作用,那就是在同时使用不同类型的XML文档的时候,可以对它们加以区分,减少互相的干扰。例如,在CSS中有个名为@namespace的规则,可以让一个文档中同样的标签元素应用不同的CSS选择器样式。
假设页面中有如下所示的HTML代码:
那么下面这段CSS可以让页面中两个<a>元素呈现不同的文字颜色,上面是蓝色的,下面是红色的,完全不冲突,效果如图所示。
你也可以通过在浏览器中输入地址https://www.htmlapi.cn/6/1-1.html访问来体验。
不过@namespace规则是个鸡肋属性,除教学作用外,没有任何实用价值。
回到正题,对于HTML文档而言,命名空间就是摆设,可有可无,但是对于SVG图形而言却并非如此,在很多场合,<svg>元素的命名空间是必不可少的。
如果SVG图形是使用<svg>元素直接内联在页面中的,那么命名空间是可以省略的,例如将下面的代码嵌入任意的HTML页面中都会渲染出一个圆形,如图所示。
但如果SVG图形需要内嵌在CSS代码中或者内联在<img>元素的src属性中,那么命名空间就不能省略,<svg>元素需要加上xmlns=“http://www.w3.org/2000/svg”。
例如,下面这段HTML代码也能有下图所示的效果,但是如果没有设置xmlns=“http://www.w3.org/2000/svg”,则图片加载异常,无法显示圆形效果。
如果大家在日常开发的时候,发现内嵌的<svg>元素无法正常显示,可以看看是不是命名空间没有设置。
<svg>元素的命名空间不仅体现在XML书写上,在DOM操作上有时候也会用到它,例如创建一个普通的HTML元素并设置某个属性值,我们直接使用setAttribute()方法就可以:但是如果创建SVG中的<image>元素并设置某个属性值,就不能使用setAttribute()方法,而需要使用setAttributeNS()方法,例如下面的代码:
1.1.2、viewBox视区盒子
还是以上面的圆形SVG为例,其设置的尺寸是100px×100px,但在实际布局的时候,我们需要其尺寸为48px×48px,于是,就会有如下所示的CSS代码设置:
但是,这样设置之后,只有部分圆形区域显示出来,如图所示。
出现这个问题是因为viewBox属性缺失,viewBox可以指定哪部分区域的图形在当前尺寸渲染,其语法如下所示:
其中,x表示视区盒子的左上角横坐标,y表示视区盒子的左上角纵坐标,width和height分别表示视区盒子的高度和宽度。
要理解这个属性,需要参考一个比较极端的案例,SVG代码如下所示:
此时的渲染效果如图所示。
如果不看viewBox,你一定会觉得诧异—SVG尺寸明明有400px×300px,而<rect>元素设置的大小只有其1/20,但是显示出来的却占据了半壁江山!不科学啊!
小小矩形之所以大显神威,就是这里的viewBox属性起了推波助澜的作用。用更形象的比喻解释就是:SVG就像我们的显示器屏幕,viewBox就像截屏工具选中的那个框,最终的呈现就是把框中的截屏内容再次在显示器中全屏显示。
更直观的解释如下:首先,假如没有viewBox属性,那么SVG图形效果就会如图所示。
随后viewBox="0 0 40 30"相当于在SVG的左上角画了个小小的框,然后放大到整个SVG尺寸上,如图所示,于是就有了最终的渲染效果。
为了便于大家理解viewBox属性是如何生效的,我做了一个分步动画演示页面,你可以通过在浏览器中输入https://www.htmlapi.cn/6/1-2.html访问来体验。
回到一开始的案例,如何让部分显示的圆形全部显示呢?设置viewBox的大小和SVG图形的大小一致就可以了,如下所示:
此时的渲染效果如图所示,虽然圆形变小了,但是显示完整了。
如果你发现SVG图形在Chrome浏览器下显示良好,但是在Safari下显示异常,则要么没设置命名空间,要么没设置viewBox。
1.1.3、preserveAspectRatio属性与缩放规则
<svg>元素还有一个非常重要的属性,名为preserveAspectRatio,可以设置SVG图形的缩放与排版规则。
需要注意的是,在正常情况下,我们是用不到preserveAspectRatio属性的,只有当CSS设置的SVG图形比例和原始SVG图形比例不一致,或者viewBox设置的视区和SVG尺寸不一致时才会用到它。
原因很简单,如果SVG的高宽比以及视区大小和原始图形一致,那么无论preserveAspectRatio设置怎样的属性值,都不会看到任何渲染变化,因为尺寸一致没有所谓的对齐说法,比例一致也没有所谓的缩放规则。
而这里的“对齐”和“缩放”正好对应preserveAspectRatio属性值的两个组成部分。
其中,第一部分的值又是由两部分组成的。前半部分表示x方向对齐,后半部分表示y方向对齐,如表所示。
使用的时候,x方向和y方向的值自由组合即可,例如xMaxYMax表示viewBox沿着SVG的右下角对齐,xMidYMid表示viewBox以水平居中位置对齐。
例如如下所示的SVG代码:
viewBox设置的高度120小于SVG的高度150,然后矩形居底渲染了,效果如图所示。
第二部分的值包括三个关键字,分别是meet、slice和none,如表所示。
例如如下所示的SVG代码:
此时的渲染效果如图所示,原本的圆形拉伸成了椭圆形。
1.1.4、SVG文件的内联与外链显示
在HTML文件中,SVG有三种内联方式,而在CSS文件中只有两种,其中最常见的就是<svg>元素直接内联,还有两种方式就是转义内联和Base64内联,这里以圆形SVG为例:
内联引入的优点是使用方便,但当多个SVG图形需要在多个地方同时使用时,内联引用会有重复加载的问题。当然,也是有解决方法的,在SVG中可以使用<symbol>元素将图像抽象出来重复使用,在CSS中可以使用CSS变量定义内联SVG资源。不过使用<symbol>元素的方法比较麻烦,因此,在HTML文件中,就存在需要使用外链方式引用SVG图形的场景。这样做有两个好处,首先,外链的SVG文件是可以被浏览器缓存的,因此可以提高网页的性能;其次,维护方便,只要修改一处,整站资源都可以替换。
在HTML中,可以实现外链SVG文件效果的元素非常多,例如<img>、<object>、<iframe>和<embed>都可以外链SVG文件并在页面中显示:
对应的效果可以通过在浏览器中输入https://www.htmlapi.cn/6/1-3.html访问来体验。
一般常用的是<img>和<object>这两个元素。
最后再提醒一下,外链的SVG文件必须要设置命名空间,否则无法在网页中正常渲染。
1.2、<canvas>元素的基本特性
<canvas>是Web领域非常重要的元素之一,和<svg>元素一样,自成体系,包含的知识非常多,绝不是一两个章节就可以讲清楚的,这里仅初步介绍<canvas>这个元素的一些特性。
首先快速了解一下<canvas>元素是如何运作的。<canvas>元素又称画布元素,默认情况下是一个透明的元素,非闭合,子元素均不会被渲染,例如:
其中的文字是不会显示的,但是如果使用DOM API获取,还是可以获取到的,例如:
回到正题,要想让<canvas>元素发挥作用,需要使用JavaScript代码在画布上绘制图形,例如绘制一个圆形,代码如下:
此时的渲染效果如图所示。
除可以使用’2d’类型的上下文外,还可以使用’webGL’类型的,例如:
由此可见,要想精通Canvas,一定要熟悉JavaScript以及对应的上百个绘制API,但通常的Web应用开发不会用到如此深入的Canvas技能,更多的是一些简单的绘制和图像处理。本书会对相关知识做简单的介绍,不过在此之前,先要了解一下Canvas画布的尺寸、坐标等基本概念。
1.2.1、尺寸
很多新手搞不清楚Canvas中的绘制尺寸和显示尺寸,其实很简单,一句话,Canvas绘制效果与CSS尺寸无关!
例如,有如下的HTML代码,其中出现了两个高度,一个是height属性设置的150,还有一个是style属性设置的视觉展示高度88:
现在,若想以<canvas>元素中心点为圆心画一个圆,则垂直坐标应该是75(height属性值150的一半),而不是44(最终视觉展示高度88的一半),代码如下所示:
此时的渲染效果如图所示。
<canvas>是个位图元素,按比例放大后Canvas图形会变模糊,因此,为适配高倍屏幕密度显示器,往往会设置<canvas>元素的绘制尺寸是显示尺寸的2倍。例如需要绘制的图像的显示尺寸是800px×600px,则<canvas>元素的width和height需要设置为1600和1200,代码如下:
对应的CSS代码则是:
接下来,所有Canvas绘制的数值和坐标都是基于1600px×1200px这个尺寸来的,需要注意的是,在Web网页中,<canvas>画布的尺寸是有限制的,并不是无穷大的,例如Chrome浏览器的最大Canvas尺寸是16384px×16384px,Safari浏览器在桌面端的最大Canvas尺寸也是16384px×16384px,但是在移动端最大Canvas尺寸是4096px×4096px。
和<svg>元素一样,如果不指定高度和宽度,则<canvas>元素会按照300px×150px的尺寸渲染,<object>和<embed>元素也采用了这个尺寸规则。
1.2.2、坐标系
Canvas的坐标起点是左上角,例如一个矩形绘制代码如下:
其中的代码fillRect(0, 0, 100, 100)中的(0, 0)就是矩形的左上角坐标,由于Canvas的坐标起点是画布的左上角,因此,上述矩形的绘制区域也就是Canvas画布的左上角,如图所示(边框为Canvas的边界)。
正常绘制的时候还好,一旦遇到需要使用Transform变换的场景,很多人就搞不明白这种坐标系了。例如要实现Canvas画布居中旋转45°的效果,需要有下面这么多步骤:
而如果使用CSS代码实现,直接用rotate:45deg就可以了,要简单得多。其中context.translate(150, 75)把原本位于左上角的坐标中心点移动到画布的(150, 75)这个位置,正好是画布默认尺寸的中心,然后再旋转坐标,接着将中心点恢复到(0, 0),此时绘制的图形才会以Canvas中心旋转。上述代码的渲染效果如图所示。
1.2.3、图像转换
HTML中的<img>元素和SVG中的<image>元素都可以直接绘制在Canvas画布上,例如:
不过<image>元素转移到Canvas上仅在学习场景下才会用到,生产环境遇到的需求往往是SVG图形转移到Canvas上,此时,需要使用<img>元素作为媒介来实现,具体实现如下(假设页面中有个<svg>元素是这样的):
此时,SVG的圆形就转移到Canvas画布上了,效果如图所示。
上图对应的效果可以通过在浏览器中输入https://www.htmlapi.cn/6/1-4.html访问来体验。
1.3、专为图像显示设计的<figure>元素和<figcaption>元素
页面中有个图像元素,无论是<svg>、<canvas>还是<img>图像元素,只要有描述信息,就特别适合使用<figure>元素和<figcaption>元素。
其中,<figcaption>元素用来显示描述信息,而<figure>元素用来包裹图像和文字信息。再配合简单的CSS代码,例如下面这段,就可以实现常见的图文排版效果了,效果如图所示。
由于<img>是最常用的HTML元素之一,因此,理论上,<figure>元素也应该是高频使用的HTML元素,如果你突然意识到<figure>元素用得并不多,那就要注意了,你的HTML代码还可以变得更好。
由于<figure>元素语义特殊且专一,因此不用担心直接使用figure标签作为选择器会有冲突问题,从这一点来看,<figure>元素还可以节约CSS类名(相比你使用<div>元素),让代码变得更加简洁易读,所以,用它准没错,谁用谁喜欢。
可以通过在浏览器中输入https://www.htmlapi.cn/6/1-5.html访问来体验。
另外,<figure>元素在水平和垂直方向都有默认的margin外间距,在文档类的排版中,这些默认样式还是很有用的,但是对于非文档类的Web开发,就不适用了,需要重置。
1.4、srcset、sizes等属性与响应式图片开发
本节介绍<img>元素的几个强大却生不逢时的HTML属性与描述符,需要花点“理解成本”,大家在阅读本节时速度可以放慢一点。
说明:本节内容涉及响应式图片开发,为了便于大家的学习,特意准备了两套图片:第一套图片仅尺寸不同,并在图片的左上角标明了尺寸,例如宽度是128px的图片(图片名是w128.jpg)在左上角会有数字128,256px和512px的图片与之类似;第二套图片呈现的信息一致,不过布局不同,一个是长图(图片名是rect.png),一个是方图(图片名是square.png)。
1.4.1、先从基础的srcset属性说起
先看一段HTML代码:
其中srcset属性值中出现的字符x是像素密度描述符,因此整段HTML代码的含义是:
- 当浏览器的屏幕密度为1的时候,显示w128.jpg这张图片。
- 当浏览器的屏幕密度不小于2的时候,显示w256.jpg这张图片。
- 当浏览器的屏幕密度不小于3的时候,显示w512.jpg这张图片。
iPhone SE手机的屏幕密度是2,因此会显示宽度为256px的图片,而iPhone 12 Pro手机的屏幕密度是3,因此会显示宽度为512px的图片,详见下图。
眼见为实,可以通过在浏览器中输入https://www.htmlapi.cn/6/1-6.html访问来体验上述效果。
srcset属性的作用不言而喻,就是在保证最佳显示效果的同时尽可能调用尺寸最合适的资源,以节约网络带宽和内存开销。
乍一看,这个srcset属性还是挺有用的,然而我就没见过谁在实际项目中使用过它,它的普及率极低。在前端技术领域,一个不错的特性没有得到应有的关注无非有两个原因:
- 可替代。
- 成本高。
其中,可替代是次要原因,例如srcset属性的作用可以被CSS中的image-set()函数以及@media媒体查询替代,代码如下:
或者:
主要原因还是开发成本高,因为准备多种规格的素材是需要耗费额外的开发精力的,日后的维护成本也不容小觑,所以业界普遍的做法都是直接使用2倍图,即:
1倍屏用户也就浪费了点流量,这个流量损耗不会被提bug,也不会影响绩效,所以开发人员多不在意,而3倍屏用户看2倍图也算清晰,以上原因造成了srcset属性无人问津,只有那些有追求的开发人员才会尽可能使用最棒的前端特性让自己的代码尽善尽美。
然而,时代在变化,工具在进步,过去,素材图需要设计师或者开发人员自己手动准备,但是现在,静态资源都开始走COS存储,任意规格的图像素材都只要修改一个参数即可获得,并没有所谓的开发成本。
因此,从现在开始,不要再以麻烦为借口了,只要接了COS对象存储,所有<img>元素的srcset属性就都可以用起来,例如:
然而,图片的响应式渲染不仅与屏幕密度有关,还与屏幕的宽度有关,例如,有一张宽度为600px的图片,由于手机设备的宽度只有320px~414px,如果此时仅仅根据屏幕密度去加载2倍图,反而会造成资源的浪费,因为600px的图片按照300多px的宽度渲染,其实就是2倍图显示。
很显然,仅用一个x描述符还不足以应对所有的响应式布局场景,于是就有了sizes属性和w描述符。
1.4.2、sizes属性和w描述符的使用场景
<img>元素的sizes属性可以设置不同尺寸下图片应该渲染的宽度,注意,是应该渲染的宽度,不是最终渲染的宽度,最终渲染的图片宽度与w描述符密切相关,尤其当w描述符设置的宽度和素材图的真实宽度不一致的时候。大家对这两句描述可能觉得有些云里雾里,不急,后面会详细解释。先看正常的sizes属性和w描述符的用法。
w描述符的使用规则很简单,图片原始宽度是多少px,就将其设置为多少w,本例中rect@1x.png的图片宽度是600px,因此后面跟着的是600w,rect@1.5x.png的图片宽度是900px,因此后面跟着的就是900w,而rect@2x.png的图片宽度是1200px,因此后面跟着的就是1200w。
至于在哪种场景下渲染哪张图片,是sizes属性及当前的显示设备共同决定的,计算公式为:将sizes属性指定的尺寸和当前的设备像素比(屏幕密度)相乘,计算的结果如果不小于设置的w描述符,则加载该图片。
因此,在以下设备中就会有如下所示的计算规则:
- 桌面浏览器,屏幕密度为1,此时匹配sizes属性中的600px宽度,实际占据像素宽度是600×1=600w,因此加载的是rect@1x.png。
- 桌面浏览器,屏幕密度为2,此时匹配sizes属性中的600px宽度,实际占据像素宽度是600×2=1200w,因此加载的是rect@2x.png。
- iPhone SE设备,屏幕密度为2,设备宽度是375px,此时匹配sizes属性中的calc(100vw-20px)宽度,其中100vw就是屏幕的宽度,因此图片应用的宽度计算值就是355px,实际占据像素宽度是355×2=710w,超过600w,小于900w,因此加载的是rect@1.5x.png。
- iPhone 13 Pro Max设备,屏幕宽度是428px,小于480px,加上屏幕密度是3,根据计算可得(428-20)×3=1224w,显然加载的是rect@2x.png。
可以看到,相比单纯地基于x描述符确定加载哪张图片,使用sizes属性和w描述符加载的图片规格更为精准。例如,在移动端,由于图片显示尺寸变小了,因而没有必要加载2倍图就能够清晰显示。图模拟了在iPhone SE设备下的加载效果,可以看到加载的是1.5倍图。
而在iPhone 13 Pro Max设备下,加载的是2倍图,效果如图所示。
眼见为实,可以通过在浏览器中输入https://www.htmlapi.cn/6/1-7.html访问来体验上述效果。
需要注意的是,上面的加载效果属于理论结果,实际加载的图片有时候会有差异,比如如果浏览器已经加载并缓存了2倍图,那么哪怕理论上匹配的是1倍图,渲染的依然是2倍图。
前文提到,w描述符的值需要和图片的原始宽度匹配,可如果不匹配会如何呢?那么实际渲染的尺寸和sizes中设置的值可能就会有明显出入,例如:
- w描述符的尺寸明显小于图片的原始尺寸,且渲染的是最大规格的图片,则实际渲染的图片尺寸会比sizes中指定的尺寸大。
- w描述符的尺寸明显大于图片的原始尺寸,且渲染的是最小规格的图片,那么实际渲染的图片尺寸会比sizes中指定的尺寸小。
尺寸偏差的倍数和w描述符与图片的原始尺寸的倍数呈线性关系,因此,在实际开发中,我们没有任何理由设置和图片原始尺寸不一样的w描述符。
1.5、实用却不常用的<picture>元素
HTML中的<picture>元素还是挺实用的,往往和<source>元素(可以多个)、<img>元素(最多一个)一同使用。
渲染的时候,浏览器优先使用<source>元素,而用<img>元素兜底。
所以,当我们需要在不同场景显示不同图片的时候,<picture>元素就特别实用。
1.5.1、不同尺寸显示不同图片
这个功能常使用HTML的media属性实现。
例如有如下HTML代码:
当浏览器的宽度小于640px时,显示的是正方形图片;当浏览器的宽度大于640px时,则会显示长方形图片,效果如图所示。
你可以通过在浏览器中输入https://www.htmlapi.cn/6/1-8.html访问来体验上述效果。
这段HTML代码特别适合用在响应式广告图的显示上。
1.5.2、不同宽度、不同屏幕密度显示不同图片
这其实是前面介绍的srcset属性的另外一种使用形式,可读性要更好一些,通常需要匹配图像尺寸(w描述符)或者设备相似比(x描述符)来显示不同的图像元素,例如:
此时1倍屏显示128px.jpg,2倍屏显示256px.jpg,3倍屏显示512px.jpg,效果如图所示。
1.5.3、不同浏览器显示不同后缀图片
浏览器会层出不穷地支持各种新的图片格式的渲染,例如早些年受关注较多的WEBP格式,近些年开始慢慢使用的AVIF格式。然而这些格式存在兼容性问题,仅有部分浏览器支持,可有时开发人员又想使用它们,怎么办呢?
<picture>元素可以帮忙,使用<source>元素将你最希望使用的图片格式放在最前面即可,最好通过type属性指定图片格式,例如:
如果浏览器支持AVIF格式,就加载体积最小的AVIF格式,如果浏览器不支持AVIF格式,但是支持WEBP格式,则加载example.webp,如果浏览器都不支持,则会加载用<img>元素兜底的example.jpg。
你可以通过在浏览器中输入https://www.htmlapi.cn/6/1-9.html访问来体验。
Chrome浏览器加载的就是example.avif(大小仅为6.96KB,同质量的JPG图片大小高达57.2KB),而Edge浏览器加载的是example.webp(大小为34.9KB),效果如图所示。
使用这种渐进增强策略可以保证绝大多数用户可以享受到最好的用户体验,因为加载的图片大小变小了,加载的速度自然也就快了。在实际开发中,我们往往会使用工具自动进行格式转换,保证永远使用最佳的图片格式,使用极低的开发成本获得更好的产品收益。
由于<picture>和<source>元素默认都是内联元素,不占据任何尺寸,因此,在参与布局实现的时候,就和使用单个<img>元素一样,所以,其实<picture>元素的上手成本是极低的。
再结合上面三个案例可以看出,<picture>元素确实挺实用,然而由于目前前端社区对HTML普遍不重视,对此知之甚少,加上有CSS和JavaScript的替代实现方案,所以导致在实际项目中,很少有人使用<picture>元素,这是很可惜的事情,希望本书的出版可以改变这个现状。
1.6、聊聊loading=“lazy”(懒加载)
在默认情况下,一个<img>元素只要设置了src图片地址,哪怕这个<img>元素是隐藏的,浏览器也会请求对应的图片资源。
这会造成很大的资源浪费,因为很多网页用户仅停留几秒,看看首屏的内容就离开了,屏幕外的图片资源显然就会白加载。所以就有了“图片懒加载”,也就是图片在用户不可见的地方就不加载,当用户真正浏览的时候才去加载。
过去图片懒加载都是使用JavaScript代码实现的,而如今非常简单,给元素设置loading="lazy"就可以了:
就是这么简单,没有任何“副作用”,可以这么说,所有的<img>元素都值得拥有一个loading=“lazy”。
1.6.1、懒加载的策略
大家需要明确一点,即设置了懒加载的图片资源何时加载是有一套比较复杂的执行策略的,不同浏览器的策略在细节上会有差异,甚至不同版本的浏览器在执行细节上也会有差异。但是,下面所说的这些大致策略都是一致的。
- 懒加载图片并不是在屏幕外就不会加载,通常要两屏或三屏开外的图片才会不加载,至于具体的缓冲高度是多少,是由屏幕尺寸、网速等众多条件共同决定的。如果网速很快,那么默认加载的图片资源就会少;如果网速慢,则浏览器会一次性加载比较多的图片,保证用户浏览的时候不会出现长时间等待图片加载的不好的体验。
- 图片懒加载的时机及数量与屏幕高度密切相关,屏幕高度越小则加载数量越少,同时,当浏览器窗口的尺寸发生变化的时候,尤其是从小窗口变成大窗口的时候,往往会触发更多的图片资源的加载。
- 并非隐藏的懒加载图片都不会执行加载,例如,连续三个隐藏的<div>元素内都有懒加载图片,很有可能第一个<div>元素里面的图片依然会加载。
- 页面中有20个懒加载图片,有可能率先加载的是后面10张图片。例如,非强制刷新的网页会自动记住上一次的滚动高度,此时,首先映入用户眼帘的是页面底部内容,势必优先加载这部分区域的图片。
- 懒加载图片的加载优先级通常是Low,如图所示,因此,那些重要的图片素材可以考虑不使用懒加载,或者设置loading=“eager”。
- 即使图片已经被缓存,懒加载也不会提前执行。
1.6.2、语法
loading属性支持两个属性值,lazy和eager。
- lazy:图片或框架懒加载,也就是在元素资源快要被看到的时候加载。
- eager:eager是急切的意思,即图片或框架资源正常加载。
上面的语法示意提到了“框架”,此框架指的正是<iframe>元素,即懒加载不仅适用于<img>元素,也适用于<iframe>元素,例如:
很多内嵌的广告就是用<iframe>元素实现的,非常有必要设置loading=“lazy”。
1.7、crossOrigin与跨域处理
支持crossOrigin属性的元素非常多,例如<audio>、<img>、<link>、<script>、<video>元素,由于crossOrigin属性多用在<img>元素上,因此放在这里介绍。
Web截图、图像特效处理、PDF导出等需求都需要将<img>元素绘制到Canvas上并转换,可若图片元素跨域了,那么无论是getImageData()方法还是toDataURL()方法,在执行的时候都会报错。
例如,使用下面的代码获取GitHub上自己头像的图片信息:
结果在Chrome浏览器下显示如下错误:
Uncaught DOMException:Failed to execute’getImageData’ on ‘CanvasRendering Context2D’:Thecanvas has been tainted by cross-origin data.
如果使用的是canvas.toDataURL()方法,则会报错:
Failed to execute ‘toDataURL’ on’HTMLCanvasElement’:Tainted canvased may not beexported.
那么有没有什么办法可以解决这个跨域限制的问题呢?可以试试crossOrigin属性。代码如下所示,新增了一句img.crossOrigin=‘’:
此时,getImageData()方法就能正常运行了,对此,我专门做了个演示页面,分别对比设置了crossOrigin属性和没有设置crossOrigin属性的效果,可以通过在浏览器中输入https://www.htmlapi.cn/6/1-10.html访问来体验。
运行结果如图所示,左侧的图片设置了crossOrigin=“”,成功执行了getImageData()方法,而右侧的图片没有设置,结果抛出了运行错误。
1.7.1、语法
crossOrigin属性支持下面两个值。
- anonymous:元素的跨域资源请求不需要凭证标志设置。
- use-credentials:元素的跨域资源请求需要凭证标志设置,意味着该请求需要提供凭证。
其中,只要crossOrigin的属性值不是use-credentials,则都会解析为anonymous,包括空字符串’',以及类似’abc’这样的字符。例如:运行结果参见下图。
use-credentials这个值日常开发中很少用到,除非是那些对JavaScript运行安全要求比较高的场景。
1.7.2、其他跨域处理方法
如果你发现设置图片的crossOrigin="anonymous"后依然无法运行,可以试试下面的方法:
无论用哪种方法,都需要提前于服务侧设置运行跨域访问,也就是设置Access-Control-Allow-Origin头信息。
1.8、使用elementtiming属性知晓图片的加载时长
相比文本内容,媒体资源的流量消耗非常惊人,很多时候会成为页面加载的性能瓶颈。传统的做法是开发人员打开浏览器的开发者工具,去查看网络请求资源的加载时长和流量开销,如果发现一张图片动辄好几MB,就会知道这里的图片资源需要优化。
然而,这种人工监控的方式非常低效,最好使用自动化识别的方法。例如,检测到图片加载时长频繁超出某个阈值,说明这张图片可能会是低效资源,可以优化。
目前,浏览器提供了原生的API,可以对页面中的图文内容元素进行性能监控,其被称为PerformanceObserverAPI,一看就是个JavaScript API,但统计元素的渲染和加载时长靠的却是一个HTML属性—elementtiming。
elementtiming属性可以设置在任何有图文内容的元素上,包括background-image(背景图),以及视频元素,相当于告诉浏览器:“喂,浏览器,这几个人本人很感兴趣,你帮我看紧,多多关注!”
此时,浏览器就会记住这些元素的加载时长、渲染时长等 各种信息。配合PerformanceObserver API,我们就可以获得这些信息了。
假设页面上有下面两个HTML元素:
此时,浏览器就会重点关注标志量为image和text的两个元素的时长相关信息。这些信息可以通过执行下面的代码进行获取。
下图就是Chrome控制台的输出结果,可以看到,不仅显示了元素的尺寸和位置信息,还显示了什么时候开始渲染,以及加载时长(图片元素才有)。
于是,如果想要监控页面中所有图片素材的加载时长,只需要给对应的<img>元素设置elementtiming属性,配合几行简单的JavaScript代码就可以了,这是浏览器原生特性,不影响业务开发,性能优异。
当然,elementtiming属性也适用于接下来要介绍的音视频资源。