如何利用javascript解析和编译现代图像模式——WebP

8,030 阅读9分钟

一张JPEG图像需要被解码、光栅化,并在屏幕上绘制。一个JavaScript包需要被下载,然后被解析、编译、执行--还有一些其他的步骤需要引擎来完成。只是要注意,这些成本并不完全相等。

这仍然是非常正确的,但在这个确切的历史时刻,它的意义要小一些。

随着一场大流行病席卷全球,我发现我的互联网已经变得相当不稳定了。幸运的是,由于网站可靠性工程师既聪明又不知疲倦,大部分的互联网仍在运行,但肯定有什么事情发生,我有一个100mbps的连接,但此刻感觉更像是3G。

这让计算有了些许转变。我们的设备仍然可以以几周前的速度解析和编译javascript,但网络速度变慢了。因此,现在电线上的原始比特数是超级重要的!

而且,网站上的图片通常远远超过200kb;一个页面有几兆字节的图片是很常见的。许多开发者(包括我自己!)倾向于不考虑媒体的大小。

令人高兴的是,有一些很低级的果实!在本教程中,我们将讨论如何利用这些果实。在本教程中,我们将看到我们如何利用 "下一代 "图像格式,如WebP。这些图片通常比我们熟悉和喜爱的传统格式(jpg、png)小2-3倍。它可以带来巨大的变化。

2021年更新

进步是不可阻挡的,一种全新的图像格式已经在Chrome和Firefox上登陆。AVIF。它从视频压缩世界中提取了一些技巧,以产生令人难以置信的小图像文件--甚至比webp还小!"。

这篇文章是在AVIF出现之前写的,但下面讨论的所有相同的技术仍然有效。你可以使用avif-cliNPM包将图像转换成这种格式,并遵循webp的说明。

认识演员

有三种格式可供我们使用。

  • JPEG 2000 - 对jpgs的迭代改进。1997年开发,主要用于电影和医疗成像。允许图像被进一步压缩,并减少伪影。

  • JPEG XR -jpeg2000 的一个表亲,由微软在2009年开发。

  • Webp - 谷歌在2010年为网络开发的一种格式,专注于使用先进的优化技术来减少文件大小。支持透明度,甚至是动画。

我们今天将花大部分时间讨论webp ,但当我们讨论浏览器兼容性时,我们将重新讨论jpeg的表兄弟。

有多大的区别?

几个月前,我在一篇文章中使用了这张图片。

Photo of cereal expiration dates

我做了一些实验,使用jpgpng 作为源图像。我用imagemin对它们进行了优化,看看这些 "复古 "格式能有多好。

结果是相当戏剧性的。 image.png 我在很多图片上测试过,它产生的文件几乎总是比优化后的图片小30-70%!

那SVG呢?

我根本就没有在SVG上测试过。SVG是一种矢量格式,意味着它是由数学指令而不是单个像素颜色组成的。如果失去了矢量格式的缩放优势,那就太可惜了,而且我怀疑在大多数情况下它实际上会增加文件的大小。

浏览器兼容性

.webp大多数浏览器中享有支持。

Browser support table for webp, showing support in every browser except Internet Exporer, Safari, or KaiOS browser

关键是,我们缺少Safari和Internet Explorer。

JPEG 2000怎么样?

Browser support table for jpeg2000, showing support in Safari and Mobile Safari, but nothing else

好吧,我们已经填上了Safari浏览器,但是还有讨厌的IE浏览器...

Browser support table for jpegxr, showing support for Internet Explorer, and Internet Explorer alone

我们已经找到了 "宾果"(caniuse bingo)!有了这三种图像格式,我们就可以完美地覆盖整个浏览器。*

让我们来看看我们如何为不同的浏览器挑选不同的格式

图片 "来拯救!

HTML有两种图像媒体元素:国际流行明星img ,以及小众的时髦艺术家picture 。*

picture 是该语言的一个较新的补充。它的主要目标是让我们根据分辨率或对特定图像格式的支持来加载不同的来源。

下面是它的样子。

<picture>
  <source srcset="/images/cereal-box.webp" type="image/webp" />
  <source srcset="/images/cereal-box.jp2" type="image/jp2" />
  <img src="/images/cereal-box.jxr" type="image/vnd.ms-photo" />
</picture>

picture 标签支持一堆source 子项。浏览器依次解析source 元素,根据type ,寻找第一个可以使用的元素。当它找到一个元素时,就会通过srcset ,计算出图片的位置,并将其换到img's。src

srcset 可以做很多复杂的事情,但令人高兴的是,对于我们的用例,我们可以把它和 一样。基本上, 是配置,它将匹配的值插入到 。src source img

例如,在Chrome浏览器中,我们最终得到的东西或多或少相当于这个。

<picture>
  <img src="/images/cereal-box.webp" />
</picture>

这种级联的来源意味着在每个浏览器上都会有一个匹配。大多数浏览器会使用webp ,Safari会使用jp2 ,而IE会使用jxr

但是IE浏览器不支持picture!

picture 元素对Internet Explorer来说是一个太现代的功能,但这段代码仍能按预期运行 😮

这是因为当浏览器碰到一个它不理解的元素时,它会把它当作一个div 。所以我们最后得到的是一堆div,和一个指向jxr 图像的<img> 标签,而这恰好是Internet Explorer所理解的格式

一个更懒的选择

上面的代码段擅长于用现代的 "下一代 "图像格式来匹配每一个可能的浏览器。但它假定这些图像以这些格式存在。

如果我们用手来创建这些图像,那就是大量的手工劳动了。如果我们自动生成它们,就会大大延长我们的构建时间;图像处理在大规模进行时是出了名的慢。

在我自己的博客上,由于IE浏览器的流量很少*,我选择了一个更懒的解决方案。

<picture>
  <source srcset="/images/cereal-box.webp" />
  <img src="/images/cereal-box.jpg" />
</picture>

我为支持它的浏览器(Chrome、Firefox、Edge)提供漂亮而微小的webp ,而对于不支持它的浏览器(IE、Safari)则退回到传统的jpg

对我来说,这是一个渐进式增强的例子。在传统的浏览器上,所有的东西都仍然有效,但图像的加载速度会慢一些。这是一个我可以接受的折衷方案。

测试它是否有效

浏览器的开发工具会一直认为该图片具有你最初给它的任何src 。如果你在元素窗格中检查它,你会看到它使用了一个.jpg

要检查它是否真的在工作,我发现最好的技巧是右击并 "另存为..."。在Chrome浏览器上,你应该得到一个 "Google WebP "文件格式,而在Safari或IE上,你应该得到一个 "JPEG"。

你也可以检查网络选项卡,以查看实际下载的是哪一种。

将图片转换为webp

谷歌已经创建了一套工具来帮助我们处理webp 文件。其中一个工具是cwebp,它可以让我们把其他图像格式转换成webp。

如果你是在MacOS上,你可以用Homebrew安装这套工具。

brew install webp

在其他平台上,我相信你需要从他们的资源库中下载适当的libwebp包

安装后,你可以像这样使用它。

cwebp -q 80 cereal.png -o cereal.webp
  • -q 80 是一个用于设置 "质量 "的标志,从1(最差)到100(最好)。你可以用不同的值进行试验。我发现70-80是最佳值。

  • cereal.png 是你要转换的输入文件的路径。

  • -o cereal.webp 是输出路径。

花时间手动做这个,没有人觉得是个好时机。在随后的一篇文章中,我将探讨使这一过程自动化的不同策略,这样我们就不必考虑这个问题,并免费获得所有的好处

用React进行抽象

一个组件是一个出色的方法,可以用<picture> 元素来抽象出一些有趣的东西。这是我一直在使用的,效果很好。

const ImgWithFallback = ({
  src,
  fallback,
  type = 'image/webp',
  ...delegated
}) => {
  return (
    <picture>
      <source srcSet={src} type={type} />
      <img src={fallback} {...delegated} />
    </picture>
  );
};

我们可以非常类似于使用img 标签的方式来使用ImgWithFallback

<ImgWithFallback
  src="/images/cereal.webp"
  fallback="/images/cereal.png"
  alt="A photo showing the expiration date on a box of Lucky Charms"
/>

与风格化组件的使用

如果你使用styled-components 或Emotion,你可能习惯于用风格化的包装器来包装图片。

const FancyImg = styled.img`
  whatever: stuff;
`;

幸好,这仍然适用于我们的ImgWithFallback 组件。我们可以像其他组件一样包装它。

const FancyImg = styled(ImgWithFallback)`
  whatever: stuff;
`;

这样做的原因是styled 帮助器的操作方式。它生成一个类并将其注入到文档的样式表中,然后将生成的类名作为一个道具传递下去。

<ImgWithFallback className="sc-some-generated-thing" />

我们把所有的属性都委托给了孩子的img 标签,所以正确的样式还是会出现在图片上。令人高兴的是,一切都像你所期望的那样工作。

盖茨比图像

如果你用Gatsby开发,gatsby-image 包已经做了一堆优化,包括转换到webp (尽管你需要选择它)。

Gatsby Image并不是要取代img ;它在使用时可能会有一些摩擦,但它也为你的麻烦提供了很多额外的魔术。

缺点

到目前为止,我发现的唯一真正的缺点是,webp ,作为一个用户,是一个恼人的工作格式。

大多数桌面软件还不支持它;例如,我不能在MacOS的预览中打开它。这意味着,如果我右击并 "另存为...... "一张webp ,我将无法查看它!

webp 转换为jpg ,相对来说是不费吹灰之力的,在谷歌上搜索一下,就会发现有许多在线供应商会免费为我提供服务。但是,这仍然是一个额外的摩擦。如果你的网站/应用程序鼓励用户下载图片,你可能不想做这种转换。

接下来的步骤

我很高兴能将我博客上的图片尺寸减少了~50%。除了在关键时刻对用户体验有好处外,我还期望这能在带宽方面为我节省一些钱。

当然,手动将我使用的每张图片转换为webp 似乎并不实际。我已经在研究如何从源jpgpng 文件中自动生成这些图片。理想情况下,这不是我需要考虑的事情,它应该在我建立网站的时候自动发生。预计很快就会看到这方面的内容 =)