本文中介绍的嵌入式图像预览(EIP)技术允许我们使用渐进式JPEG,Ajax和HTTP范围请求在延迟加载期间加载预览图像,而无需传输其他数据。
低质量图像预览(LQIP)和基于SVG的变体SQIP是延迟图像加载的两种主要技术。两者的共同之处在于您首先生成低质量的预览图像。这将显示模糊,然后由原始图像替换。如果您可以向网站访问者呈现预览图像而无需加载其他数据,该怎么办?
根据规范,主要使用延迟加载的JPEG文件可以以这样的方式存储包含在其中的数据,即首先显示粗略的然后显示详细的图像内容。在加载(基线模式)期间,不是从上到下构建图像,而是可以非常快速地显示模糊图像,其逐渐变得更清晰和更锐利(渐进模式)。
除了由更快速显示的外观提供的更好的用户体验之外,渐进式JPEG通常也小于其基线编码的对应物。对于大于10 kB的文件,根据雅虎开发团队的Stoyan Stefanov,使用渐进模式时,图像较小的概率为94%。
如果您的网站包含许多JPEG,您会注意到即使是渐进式JPEG也会依次加载。这是因为现代浏览器只允许六个同时连接到域。因此,单独的渐进式JPEG不是为用户提供最快的页面印象的解决方案。在最坏的情况下,浏览器将在开始加载下一个图像之前完全加载图像。
这里提出的想法现在只是从服务器加载渐进式JPEG的这么多字节,您可以快速获得图像内容的印象。稍后,在我们定义的时间(例如,当加载了当前视口中的所有预览图像时),应该加载图像的其余部分,而不再请求已经为预览请求的部分。
遗憾的是,您无法告诉img
属性中的标记应该在什么时间加载多少图像。但是,使用Ajax,只要提供映像的服务器支持HTTP范围请求,这是可能的。
使用HTTP范围请求,客户端可以在HTTP请求标头中通知服务器,所请求文件的哪些字节将包含在HTTP响应中。每个较大的服务器(Apache,IIS,nginx)都支持此功能,主要用于视频播放。如果用户跳到视频的末尾,则在用户最终看到所需部分之前加载完整视频效率不高。因此,服务器仅请求用户请求的时间周围的视频数据,以便用户可以尽可能快地观看视频。
1.创建渐进式JPEG
渐进式JPEG由几个所谓的扫描段组成,每个扫描段包含最终图像的一部分。第一次扫描仅非常粗略地显示图像,而文件后面的图像会向已加载的数据添加越来越多的详细信息,最终形成最终外观。
单个扫描的确切外观由生成JPEG的程序决定。在类似的命令行程序cjpeg依据从mozjpeg项目,甚至可以定义哪些数据这些扫描包含。但是,这需要更深入的知识,这超出了本文的范围。为此,我想参考我的文章“ Finally Understanding JPG ”,它讲授了JPEG压缩的基础知识。在wizard.txt中解释了必须在扫描脚本中传递给程序的确切参数mozjpeg项目。在我看来,默认情况下mozjpeg使用的扫描脚本(七次扫描)的参数是快速渐进结构和文件大小之间的良好折衷,因此可以采用。
要将我们的初始JPEG转换为渐进式JPEG,我们使用jpegtran
mozjpeg项目。这是一个对现有JPEG进行无损更改的工具。Windows和Linux的预编译版本可在此处获得:https://mozjpeg.codelove.de/binaries.html。如果您出于安全考虑而喜欢安全地玩,那么最好自己构建它们。
从命令行我们现在创建渐进式JPEG:
我们想要构建渐进式JPEG的事实由jpegtran假定,并且不需要明确指定。图像数据不会以任何方式更改。仅改变文件内的图像数据的排列。
理想情况下,应从JPEG中删除与图像外观无关的元数据(例如Exif,IPTC或XMP数据),因为如果元数据解码器位于图像内容之前,则相应的段只能被元数据解码器读取。由于这个原因我们无法将它们移动到文件中的图像数据后面,因此它们已经与预览图像一起传送并相应地放大第一个请求。使用命令行程序,exiftool
您可以轻松删除这些元数据:
如果您不想使用命令行工具,还可以使用在线压缩服务compress-or-die.com生成不带元数据的渐进式JPEG。
2.确定第一个HTTP范围请求必须加载预览图像的字节偏移量
JPEG文件被分成不同的段,每个段包含不同的组件(图像数据,诸如IPTC,Exif和XMP之类的元数据,嵌入的颜色配置文件,量化表等)。这些段中的每一个都以由十六进制FF
字节引入的标记开始。接下来是一个表示段类型的字节。例如,D8
完成标记到SOI标记FF D8
(图像开始),每个JPEG文件开始使用。
每次扫描开始都由SOS标记(扫描开始,十六进制FF DA
)标记。由于SOS标记后面的数据是熵编码的(JPEG使用霍夫曼编码),因此FF C4
在SOS段之前还有另一个具有解码所需的霍夫曼表(DHT,十六进制)的段。因此,渐进式JPEG文件中的我们感兴趣的区域由交替的霍夫曼表/扫描数据段组成。因此,如果我们想要显示图像的第一次非常粗略的扫描,我们必须FF C4
从服务器请求直到第二次出现DHT段(十六进制)的所有字节。
在PHP中,我们可以使用以下代码来读取所有扫描到数组所需的字节数:
我们必须将2的值添加到找到的位置,因为浏览器只在遇到新标记时才会呈现预览图像的最后一行(如前所述,它包含两个字节)。
由于我们对此示例中的第一个预览图像感兴趣,因此我们找到了正确的位置$positions[1]
,我们必须通过HTTP范围请求来请求该文件。要请求具有更好分辨率的图像,我们可以使用数组中的稍后位置,例如$positions[3]
。
3.创建前端JavaScript代码
首先,我们定义一个img
标记,我们给出刚刚评估的字节位置:
与延迟加载库的情况一样,我们不src
直接定义属性,因此浏览器在解析HTML代码时不会立即开始从服务器请求图像。
使用以下JavaScript代码,我们现在加载预览图像:
此代码创建一个Ajax请求,该请求告诉HTTP范围标头中的服务器将文件从开头返回到data-bytes
…中指定的位置,而不是更多。如果服务器理解HTTP范围请求,它将以blob的形式返回HTTP-206响应(HTTP 206 =部分内容)中的二进制图像数据,我们可以使用它生成浏览器内部URL createObjectURL
。我们将此网址用作src
我们的img
代码。因此我们加载了预览图像。
我们将blob另外存储在属性中的DOM对象上src_part
,因为我们将立即需要这些数据。
在开发人员控制台的网络选项卡中,您可以检查我们是否未加载完整的图像,但只是一小部分。此外,应显示blob URL的加载,大小为0字节。
由于我们已经加载了原始文件的JPEG标题,因此预览图像的大小正确。因此,根据应用,我们可以省略img
标签的高度和宽度。
替代方案:内嵌加载预览图像
出于性能原因,还可以将预览图像的数据直接作为数据URI传输到HTML源代码中。这节省了传输HTTP头的开销,但base64编码使图像数据增加了三分之一。如果您使用gzip或brotli等内容编码提供HTML代码,则会对此进行相对化处理,但您仍应将数据URI用于小型预览图像。
更重要的是,预览图像立即可用,并且在构建页面时用户没有明显的延迟。
首先,我们必须创建数据URI,然后我们在img
标记中使用它src
。为此,我们通过PHP创建数据URI,其中此代码基于刚刚创建的代码,该代码确定SOS标记的字节偏移:
创建的数据URI现在直接插入到`img`标签中src
:
当然,JavaScript代码也必须适应:
我们不得不通过Ajax请求请求数据,而是立即收到blob,在这种情况下,我们必须自己从数据URI创建blob。为此,我们从不包含图像数据的部分释放数据URI : data:image/jpeg;base64
. 我们用atob
命令解码剩余的base64编码数据。为了从现在的二进制字符串数据创建blob,我们必须将数据传输到Uint8数组,这确保数据不被视为UTF-8编码的文本。从这个数组中,我们现在可以使用预览图像的图像数据创建二进制blob。
因此,我们不必为此内联版本调整以下代码,我们data-bytes
在img
标记上添加属性,在前面的示例中包含必须加载图像的第二部分的字节偏移量。
在开发人员控制台的网络选项卡中,您还可以在此处检查加载预览图像不会生成其他请求,而HTML页面的文件大小已增加。
加载最终图像
在第二步中,我们在两秒后加载图像文件的其余部分作为示例:
在Range标题中,这次我们指定要从预览图像的结束位置到文件末尾请求图像。第一个请求的答案存储在src_part
DOM对象的属性中。我们使用两个请求的响应来创建一个新的blob per new Blob()
,其中包含整个图像的数据。从此生成的blob URL再次用作src
DOM对象。现在图像已完全加载。
此外,我们现在可以再次检查开发者控制台的网络选项卡中的加载大小。
原型
在以下URL中,我提供了一个原型,您可以在其中试验不同的参数:http://embedded-image-preview.cerdmann.com/prototype/
可以在此处找到原型的GitHub存储库:https://github.com/McSodbrenner/embedded-image-preview