减小图片加载布局抖动和在 Hexo 中的最佳实践

图片加载容易带来布局抖动,图片懒加载更是容易凸显这样的布局变化。笔者最近在给自己的博客测试图片懒加载功能时,深受其扰。欲毙之。

网页其余部分在图片加载时产生位移

图源 davidecalignano.it

基础最佳实践

web.dev 给出的现代最佳实践中,开发者应当

  • 在 HTML 里为图片元素设置不含单位的宽高;
  • 在 CSS 里定义至少一个维度的具体大小,将未规定维度大小值设为 auto

现代浏览器会自动根据图片元素 HTML 提供的宽高计算 aspect-ratio,因此只需在 CSS 里定义一个维度的具体大小,浏览器就会自动将不定值维度的大小计算出来。其效果类似于:

img {
  aspect-ratio: attr(width) / attr(height);
}

另外,在支持 aspect-ratio CSS 属性之前,部分浏览器(如 Chrome 79-87)就已经在使用这两个 HTML 属性内部计算长宽比了。不过效果较差,往往在比如图片加载失败时、使用了 loading="lazy" 时失效,减少不了多少布局抖动。

在 Hexo 中的最佳实践

那么我们应该怎样为每张图片设置长宽呢?手动设置似乎是不太现实的。虽然笔者的博客目前只有十几张图片,但设置到第 3 张时就已经累趴下了。Markdown 并没有统一的广泛的图片大小设置语法,而且写作时设置这些大多数时候都是一件没有必要且非常麻烦的事情。

而在搜索现有 Hexo 文档和插件后,我没有发现这样一款插件,可以去嗅探博客中用到的图片的大小,并设置在相应的 <img> 元素上。因此自己写了一个 hexo-filter-probe-image-size,通过 npm 安装。

在安装完成后,在 Hexo 配置文件中将其开启。默认情况下,这个插件使用网站 images/ 目录下的同名图片的大小作为源于网络的图片的大小,而不会发送真正的网络请求。

# In _config.yml
probe_image_size:
  enable: true

有人选择为所有图片指定一个默认大小,原因是图片散布在各处。笔者也是一样,图片散布在各处。为了解决这个问题,实现了 proxies 选项配置各图片的不同位置。这个配置项会取代上一段所述的默认行为。

probe_image_size:
  enable: true
  proxies:
    # 名为 example.png 的图片
    # 在 D:/Writings/example/ 里
    - name: Element module docs
      match: ^.+/(?=example.png$)
      target: D:/Writings/example/

    # 开头是 Donate- 或 Donates 的图片
    # 在 E:/Documents/Donate/ 里
    - name: Donates
      match: ^.+/(?=Donate[s-][^/]+$)
      target: E:/Documents/Donate/

    # 网络图片都映射到 D:/CDN/img/
    - name: HTTP to local
      match: ^(https?:)?//.+/
      target: D:/CDN/img/

这也是一个整理网站图片的好时机。

每个 match 子设置项表示要匹配路径的正则表达式,每个 target 表示应解析到的路径。插件操作 嗅探成功的原来没有 widthheight 属性的 <img> 元素。更详细的解释和示例请访问该仓库的 README 自述文档。

因为绝大多数图片的大小信息位于文件头;笔者博客的图片只有 14 张,都能和本地文件对应上;插件并行处理,在笔者的电脑上这些图片大小的嗅探总时长仅约 41 ms。抛去初次处理用时,每张图片添加约 1-2 ms 的嗅探时间。

现在在 PageSpeed Insights 上测试笔者博客 sliphua.work 加载时布局抖动为 0。整体的首次内容展示也快了很多,快了多少因为之前没测,不知道。能用就行。