布局偏移

累计布局偏移 Cumulative Layout Shift(CLS)是一项 Web 指标。

布局偏移如图所示,元素突然变化的高度影响了用户交互体验:

CLS problem

一般给图片一个高度就可以了,但最好是能自动化,而不是手写。

看到了这篇博文 累计布局偏移修复方案改进 —— 自动生成图片宽高,不过他是从腾讯云对象存储获取的图片宽高,而我都将图片存储在了 Hugo 配置目录的 static/img/ 文件夹。

我平时在 Markdown 里都是写的相对路径:

![](/img/example.webp)

解决本地图片的偏移问题

参考 How to prevent CLS in Hugo,利用 Hugo 的 Render Hooks 功能重新渲染图片相关代码,获取图片的宽高,在 <img> 标签里加上 widthheight 属性。

不知道为什么照他的办法,在配置中加入了 mounts 相关属性,仍然不起作用,不太会前端和 Hugo,摸索着改了改。

创建 layouts/_default/_markup/render-image.html 文件:

<p>
{{ if hasPrefix .Destination "/img" }}
    {{ $img := os.ReadFile (path.Join "/static" .Destination) | resources.FromString .Destination }}
    {{ if ne $img.MediaType.SubType "svg" }}
        <img loading="lazy"
            src="{{ .Destination | safeURL }}"
            alt="{{ .Text }}"
            width="{{ $img.Width }}"
            height="{{ $img.Height }}"
            style="max-width: 100%; height: auto;"
        />
    {{ end }}
    {{ if or (not $img) (eq $img.MediaType.SubType "svg") }}
        <img loading="lazy" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
    {{ end }}
{{ else }}
    <img loading="lazy" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
{{ end }}
</p>

在他的基础上手动加上了 /static 地址前缀,不然这个 os.ReadFile 方法一直报错;

加上了 style="max-width: 100%; height: auto;",保持宽度超过父容器的图片的比例;

套了个 if else,只处理图片地址为 /img 开头的本地图片,并可以显示网络图片,但是网络图片仍然会产生偏移。


我用的是 Hugo PaperMod 主题,主页的封面图也存在这个问题,得修改 cover.html,看不太懂,我看上面那位博主是修改的这里,确实起作用。

修改最外层的 {{- else }} 里的内容(Hugo PaperMod 6.0 版本),和之前的一样,就是替换一下 srcalt 的属性值:

{{- else }}{{/* For absolute urls and external links, no img processing here */}}
    {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        rel="noopener noreferrer">{{ end -}}
        <!-- <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}"> -->
        <!-- 修改,将上面那行改成下面这些: -->
        {{ if hasPrefix .Params.cover.image "/img" }}
            {{ $img := os.ReadFile (path.Join "/static" .Params.cover.image) | resources.FromString .Params.cover.image }}
            {{ if ne $img.MediaType.SubType "svg" }}
                <img loading="lazy"
                    src="{{ .Params.cover.image | safeURL }}"
                    alt="{{ $alt }}"
                    width="{{ $img.Width }}"
                    height="{{ $img.Height }}"
                    style="max-width: 100%; height: auto;"
                />
            {{ end }}
            {{ if or (not $img) (eq $img.MediaType.SubType "svg") }}
                <img loading="lazy" src="{{ .Params.cover.image | safeURL }}" alt="{{ $alt }}" />
            {{ end }}
        {{ else }}
            <img loading="lazy" src="{{ .Params.cover.image | safeURL }}" alt="{{ $alt }}" />
        {{ end }}
{{- end }}