前言

我以前一直使用Hexo框架。但是随着文章数量的不断增加,自定义项目也是越来越多,加上NPM插件也是冲突不断,已经不敢随意安装新的插件,维护难度大大上升。前Hexo博客项目文件已然是“屎山”一片。

俗话说破而后立,趁着这段时间比较空闲,我直接切换到了Hugo框架。Hugo给我的第一印象也是最深印象便是它的速度非常的快,相比于Hexo快了几倍不止,当然也发现了一些不适应的地方:原博客的webp图片压缩和水印功能都是插件自动生成的,本篇就是利用hugo的高度自定义和丰富的图像处理api还原博客的图片结构。

Hugo图片自定义原理

Hugo有一个Render Hooks功能,可以替换部分组件生成样式,其中就包含了images图片资源。下面是官方介绍,有兴趣的可以去仔细了解。

When rendering Markdown to HTML, render hooks override the conversion. Each render hook is a template, with one template for each supported element type:

针对图片而言,我们需要创建或者修改layouts/_default/_markup/render-image.html文件内容,以满足自定义需求。如果你是第一次知道这个概念,请先在你博客根目录下的相应路径创建这个文件,注意不是在主题theme目录下。

创建图片自定义文件

接下来我会针对每一个处理给出参考文章和代码,文章的最后我会给出详细注释的全部代码,每一小节的代码仅供参考,类似于伪代码,不能直接使用。

Webp压缩

效果展示

一个静态博客的博文内存占用中占大头的就是图片了,动辄100kb以上的图片让本就不快的博客雪上加霜(博客托管在免费的Cloudflare上面,国内速度感人)。将常见的png、jpg等图片文件压缩成webp格式可以显著减少占用。

以上图为例,压缩前占用39.35kb,压缩后占用8.87kb。

压缩前后对比

原理解析

参考Hugo图片自动转webp的方法,主要是利用image.Resize方法,官方文档表示可以指定具体的长宽以及格式。

格式转换

参考文章加上了识别图片EXIF方向,防止转换格式后图片方向改变。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{ with $rawImage.Exif }}
  {{ if eq .Tags.Orientation 1 }}
      {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp") }}
  {{ else if eq .Tags.Orientation 3 }}
      {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp r180") }}
  {{ else if eq .Tags.Orientation 6 }}
      {{ $image = $image.Resize (print $image.Height "x" $image.Width " webp r270") }}
  {{ else if eq .Tags.Orientation 8 }}
      {{ $image = $image.Resize (print $image.Height "x" $image.Width " webp r90") }}
  {{ end }}
{{ end }}

添加水印

方法来源

在中文互联网上基本上没找到我需要的内容,最终在PaperMod主题(也就是本博客使用的主题)作者的blog里找到了一篇三年前的文章:Watermarking images with HUGO

主要思路是利用image.Filterimage.Overlay方法,将水印图片覆盖到每一张图片上。

1
2
{{- $logo := $logo.Resize (printf "%.0fx jpg" $size) }}
{{- $image := $image.Filter (images.Overlay $logo (sub $image.Width $logo.Width) (sub $image.Height $logo.Height) ) }}

这就要求水印图片结构简单,推荐使用网站logo或者博客网址来制作水印覆盖图。我的水印是通过这个工具网站进行制作的。

代码展示

原作者将水印覆盖到图片的右下角,我修改为居中透明倾斜放置,既不会过多的影响阅读体验,又能更好的保护自己的图片版权。

1
2
3
4
5
{{ $size := math.Round (mul $image.Height 0.80) }}
{{ $size := cond (ge $size 250) $size 250.0 }}
{{ $logo := $logo.Filter (images.Opacity 0.5)  }}
{{ $logo := $logo.Resize (printf "%.0fx png r30" $size) }}
{{ $image = $image.Filter (images.Overlay $logo (div (sub $image.Width $logo.Width) 2) (div (sub $image.Height $logo.Height) 2)) }}

需要计算原图的长宽来确定logo位置,然后使用image.Opacity方法设置透明度,最后居中放置即可。

使用Fancybox

我的博客图片通常包含大量的文字信息,如果不能放大查看原图会很影响体验。恰巧fancybox就是解决此类问题的js库。官网介绍如下:

Fancybox is the ultimate JavaScript lightbox alternative that sets the standard for premium user experience in multimedia display. Supports images, videos, maps, inline content, iframes and any other HTML content.

参考教程

功能很强大,这里就只展示用于图片画廊gallery功能。这一步恰好可以参考我以前为Hexo Cactus主题编写的教程:

Cactus使用上Fancybox图片放大
2022-02-20   #cactus #beautify #fancybox
将fancybox安装到hexo主题cactus上的一些经验,供大家参考。fancybox可以解决cactus主题图片不能放大的问题,感谢fancyapps做出的贡献。

代码展示

特别注意首先需要导入相关的css文件与js文件,然后修改img上层的a标签。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<a data-fancybox="gallery" data-src="{{ $image.RelPermalink }}" data-caption="{{ .Text }}">
    <picture>
        <source type="image/webp" srcset="{{ $image.RelPermalink }}">
        <img
            width="{{ $rawImage.Width }}px"
            height="{{ $rawImage.Height }}px"
            style="width: 100%; height: 100%; max-height: 36rem;"
            loading="lazy"
            src="{{ $rawImage.RelPermalink }}"
            alt="{{ .Text }}"
            {{ with .Title }} title="{{ . }}" {{ end }}
        />
    </picture>
</a>

添加picture标签是为了适配不能解析webp文件的老设备显示原图,提高代码兼容性。加上width与height参数是为了减少CLS的影响。

总结

代码不能保证可以百分百直接套用,更好的方法是一步一步学习原理,结合自己的主题特点进行修改。

代码汇总

下面的代码我会尝试进行详细的注释,使用时删除注释即可。如果还有其他疑问,可以评论区留言。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{{/* 获取当前页面匹配.Destination的资源图像 */}}
{{ $image := .Page.Resources.GetMatch .Destination }}
{{/* 从资源中获取logo图像,透明背景图片效果最好 */}}
{{/* 该图像需要放置在根目录的assets/images目录下 */}}
{{ $logo := (resources.Get "images/logo.png") }}
{{/* 检查图像的媒体类型是否不是gif或svg */}}
{{ if not (in (slice "gif" "svg") $image.MediaType.SubType) }}
    {{/* 保留原始图像的引用 */}}
    {{ $rawImage := $image }}
    {{/* 计算图像高度的80%,并确保最小值为250 */}}
    {{ $size := math.Round (mul $image.Height 0.80) }}
    {{ $size := cond (ge $size 250) $size 250.0 }}
    {{/* 调整logo的透明度为50% */}}
    {{ $logo := $logo.Filter (images.Opacity 0.5)  }}
    {{/* 调整logo的大小,高度为计算出的$size,保持纵横比,圆角半径为30 */}}
    {{ $logo := $logo.Resize (printf "%.0fx png r30" $size) }}
    {{/* 如果原始图像有EXIF信息 */}}
    {{ with $rawImage.Exif }}
        {{/* 根据图像的方向调整图像的大小和旋转 */}}
        {{ if eq .Tags.Orientation 1 }}
            {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp") }}
        {{ else if eq .Tags.Orientation 3 }}
            {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp r180") }}
        {{ else if eq .Tags.Orientation 6 }}
            {{ $image = $image.Resize (print $image.Height "x" $image.Width " webp r270") }}
        {{ else if eq .Tags.Orientation 8 }}
            {{ $image = $image.Resize (print $image.Height "x" $image.Width " webp r90") }}
        {{ end }}
    {{ else }}
        {{/* 如果没有EXIF信息,则只调整大小,不旋转 */}}
        {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp") }}
    {{ end }}
    {{/* 在图像上添加logo水印,位置在图像的中心 */}}
    {{ $image = $image.Filter (images.Overlay $logo (div (sub $image.Width $logo.Width) 2) (div (sub $image.Height $logo.Height) 2)) }}
    {{/* 创建符合fancybox要求的data属性的链接,展示处理后的图像 */}}
    <a data-fancybox="gallery" data-src="{{ $image.RelPermalink }}" data-caption="{{ .Text }}">
        <picture>
            <source type="image/webp" srcset="{{ $image.RelPermalink }}">
            <img
                width="{{ $rawImage.Width }}px"
                height="{{ $rawImage.Height }}px"
                style="width: 100%; height: 100%; max-height: 36rem;"
                loading="lazy"
                src="{{ $rawImage.RelPermalink }}"
                alt="{{ .Text }}"
                {{ with .Title }} title="{{ . }}" {{ end }}
            />
        </picture>
    </a>
{{ else }}
    {{/* 对于gif或svg图像,不进行webp压缩处理 */}}
    <a data-fancybox="gallery" data-src="{{ $image.RelPermalink }}" data-caption="{{ .Text }}">
        <img
            width="{{ $image.Width }}px"
            height="{{ $image.Height }}px"
            style="width: 100%; height: 100%; max-height: 36rem;"
            loading="lazy"
            src="{{ $image.RelPermalink }}"
            alt="{{ .Text }}"
            {{ with .Title }} title="{{ . }}" {{ end }}
        />
    </a>
{{ end }}

致谢

Hugo: Image render hooks

Hugo图片自动转webp的方法