惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
DataBreaches.Net
T
Tailwind CSS Blog
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
F
Full Disclosure
V2EX - 技术
V2EX - 技术
N
News and Events Feed by Topic
Help Net Security
Help Net Security
L
LangChain Blog
Y
Y Combinator Blog
宝玉的分享
宝玉的分享
Google Online Security Blog
Google Online Security Blog
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
B
Blog RSS Feed
N
Netflix TechBlog - Medium
N
News | PayPal Newsroom
TaoSecurity Blog
TaoSecurity Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Vulnerabilities – Threatpost
B
Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
AI
AI
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
G
GRAHAM CLULEY
Vercel News
Vercel News
罗磊的独立博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
博客园 - 司徒正美
C
CERT Recently Published Vulnerability Notes
GbyAI
GbyAI
Scott Helme
Scott Helme
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Troy Hunt's Blog
A
About on SuperTechFans
P
Privacy International News Feed

流动

对口型视频合成方案对比:Wav2Lip、VideoReTalking 与 MuseTalk 2026 音色克隆方案对比:IndexTTS-2、CosyVoice、GPT-SoVITS、Fish Speech、VoxCPM 部署与实测 AI Agent折腾记(OpenClaw / Hermes Agent) 我的2025年 大连之行 回家收麦 六一儿童节爬长城 Golang database/sql 数据库断线自动重连机制解析 Golang默认Http Client导致的cannot assign requested address错误 清明踏春,爬山看海 购入小牛G400T电动车 北京的三月飞雪 wrenAI本地LLM模型部署 天津一日游 2024年终总结 停止使用staticfile.org服务 使用 ImageMagick 自动添加水印,保护图片版权 如何注册一个.sol域名 奥森公园半日游 昌平42公里骑行绿道打卡 十月一日爬慕田峪长城 当Hugo遇上AVIF,优化图片加载 博客被恶意镜像 Github Pages 部署流程解析 搭建个人锻炼页面 你好 Follow 中秋爬山 Google Adsense的审核之旅 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 一次简短的青岛之行 解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway 302跳转的跨域问题(CORS) GORM增加sqlcommenter特性 源码分析:GORM是如何生成sql的 工银亚洲网银密码重置 加速Cloudflare访问 2023年终总结 2023年12月北京暴雪记录 加速Google Analytics 使用Google Indexing API加速博客收录 在Netlify上部署Twikoo评论系统 利用Github Actions定时抓取微博 北大口腔牙周刮治记录 故乡回忆之旅 解决Golang使用go get安装包后找不到可执行文件的问题 修正Hugo的JSON Feed格式 我的学车之路 将博客部署到星际文件系统(IPFS) 新冠疫情后的第一个春节 第一次清理键盘 2022年终总结 去掉Cloudflare烦人的email-decode.min.js请求 累计布局偏移修复方案改进 —— 自动生成图片宽高 优化博客的累计布局偏移(CLS)问题 将博客部署到Cloudflare Pages 奥林匹克公园向日葵之旅 记第二次洗牙 记录2022年海淀幼升小 Golang解析json的一个问题 疫情下的生活 整理下博客的一些调整 疫情下的五一假期 自己动手,更换thinkpad x1硬盘 二刷百望山 带娃游颐和园 博客架构说明 难得的清明假期 十一年的等待,终于拿到了liudon.com域名 被隔离的一周 mysql中字符串和整型自动转换的问题 一次惊心动魄的Mysql更新操作 如何在北京公积金网站上修改婚姻状况 PHP7.2编译安装后没有php.ini文件的问题 检测网站支持的SSL/TLS协议版本 记一次难忘的手术经历 十一假期经历 Swoft 框架运行分析(五) —— ConsoleProcessor模块分析 Swoft 框架运行分析(四) —— EventProcessor模块分析 一个git submodule update引发的问题 一个Curl的耗时长的问题 Swoft 框架运行分析(三) —— BeanProcessor模块分析 Swoft 框架运行分析(二) —— AnnotationProcessor模块分析 Swoft 框架运行分析(一) BCMath 与 科学计数 Flink Could Not Resolve Resourcemanager Address 解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错 关于本站 2019,新开始
使用Hugo实现响应式和优化的图片
Liudon · 2023-12-10 · via 流动

继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。

问题

在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高

经过一段运行后,发现这里有一个弊端。

Run hugo --gc --minify --cleanDestinationDir
Start building sites … 
hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio

ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer
ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer

随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。

失败的时候,需要手动重跑构建,自动化发布卡壳了。

优化

经过一番搜索,发现其实Hugo本身是支持图片处理能力的。

Image processing

Resize, crop, rotate, filter, and convert images.

https://gohugo.io/content-management/image-processing/

下面以我使用的PaperMod主题为例,讲下如何通过image processing实现图片响应式优化。

image processing需要用到Page bundles,所以文章目录结构需要调整, 将一篇文章的资源(md文件,图片等)放在一个目录下。

content/
├── posts
│   ├── my-post
│   │   ├── content1.md
│   │   ├── content2.md
│   │   ├── image1.jpg
│   │   ├── image2.png
│   │   └── index.md
│   └── my-other-post
│       └── index.md

目录结构调整完毕后,接下来修改图片显示文件代码。

这里需要生成webp格式图片,所以需要使用hugo的extended版本

PagerMod主题涉及到图片显示的一共三个文件:

  • _default/_markup/render-image.html,对应markdown图片语法解析。

    {{- $respSizes := slice 480 720 1080 -}} /*生成的图片规格*/
    {{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
    
    {{- $holder := "GIP" -}}
    {{- $hint := "photo" -}}
    {{- $filter := "box" -}}
    
    {{- $Destination := .Destination -}}
    {{- $Text := .Text -}}
    {{- $Title := .Title -}}
    
    /*内容图片响应式开关配置,默认为true*/
    {{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
    
    {{ with $src := .Page.Resources.GetMatch .Destination }}
        {{- if $responsiveImages -}}
            <picture>
                /*只有使用了hugo扩展版本的,才生成webp格式图片*/
                {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
                <source type="image/webp" srcset="
                {{- with $respSizes -}}
                    {{- range $i, $e := . -}}
                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
                    {{- end -}}
                {{- end -}}" sizes="{{ $dataSzes }}" />
                {{- end -}}
                <source type="{{ $src.MediaType.Type }}" srcset="
                {{- with $respSizes -}}
                    {{- range $i, $e := . -}}
                        {{- if ge $src.Width . -}}
                            {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
                        {{- end -}}
                    {{- end -}}
                {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
                <img src="{{ $Destination | safeURL }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
            </picture>
        {{- else }}
            <img src="{{ $Destination | safeURL }}" width="{{ $src.Width }}" height="{{ $src.Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        {{- end }}
    {{ end }}
    
  • partials/cover.html,对应文章封面解析。

    {{- $respSizes := slice 480 720 1080 -}}
    {{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
    
    {{- $holder := "GIP" -}}
    {{- $hint := "photo" -}}
    {{- $filter := "box" -}}
    
    {{- with .cxt}} {{/* Apply proper context from dict */}}
    {{- if (and .Params.cover.image (not $.isHidden)) }}
    {{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
    <figure class="entry-cover">
        /*封面响应式图片配置开关,默认为true*/
        {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        {{- if $cover -}}{{/* i.e it is present in page bundle */}}
            {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
                rel="noopener noreferrer">{{ end -}}
            {{- if $responsiveImages -}}
            <picture>
            {{- if and hugo.IsExtended (ne $cover.MediaType.Type "image/webp") -}}
            <source type="image/webp" srcset="
            {{- with $respSizes -}}
                {{- range $i, $e := . -}}
                    {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
                {{- end -}}
            {{- end -}}" sizes="{{ $dataSzes }}" />
            {{- end -}}
            <source type="{{ $cover.MediaType.Type }}" srcset="
            {{- with $respSizes -}}
                {{- range $i, $e := . -}}
                    {{- if ge $cover.Width . -}}
                        {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
                    {{- end -}}
                {{- end -}}
            {{- end -}}, {{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" sizes="{{ $dataSzes }}" />
    
            <img loading="lazy" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
                width="{{ $cover.Width }}" height="{{ $cover.Height }}">
            </picture>
            {{- else }}{{/* Unprocessable image or responsive images disabled */}}
            <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
            {{- end }}
        {{- 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 }}">
        {{- end }}
        {{- if $addLink }}</a>{{ end -}}
        {{/*  Display Caption  */}}
        {{- if not $.IsHome }}
            {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        {{- end }}
    </figure>
    {{- end }}{{/* End image */}}
    {{- end -}}{{/* End context */ -}}
    
  • shortcodes/figure.html,对应文章内figure语法解析。

    {{- $respSizes := slice 480 720 1080 -}}
    {{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
    
    {{- $holder := "GIP" -}}
    {{- $hint := "photo" -}}
    {{- $filter := "box" -}}
    
    {{ $src := .Get "src" }}
    {{ $align := .Get "align" }}
    {{ $alt := .Get "alt" }}
    {{ $caption := .Get "caption" }}
    
    /*内容图片响应式开关配置,默认为true*/
    {{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
    
    <figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
            {{- if eq (.Get "align") "center" }}align-center {{ end }}
            {{- with .Get "class" }}{{ . }}{{- end }}"
    {{- end -}}>
        {{- if .Get "link" -}}
            <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
        {{- end }}
        {{ with $src := $.Page.Resources.GetMatch (.Get "src") }}
        <picture>
            {{- if $responsiveImages -}}
                {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
                <source type="image/webp" srcset="
                {{- with $respSizes -}}
                    {{- range $i, $e := . -}}
                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
                    {{- end -}}
                {{- end -}}" sizes="{{ $dataSzes }}" />
                {{- end -}}
                <source type="{{ $src.MediaType.Type }}" srcset="
                {{- with $respSizes -}}
                    {{- range $i, $e := . -}}
                        {{- if ge $src.Width . -}}
                            {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
                        {{- end -}}
                    {{- end -}}
                {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
            {{- end }}
            <img loading="lazy" src="{{ $src }}{{- if eq ($align) "center" }}#center{{- end }}"
            {{- if or ($alt) ($caption) }}
            alt="{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}"
            {{- end -}}
            {{- with $src.Width -}} width="{{ . }}"{{- end -}}
            {{- with $src.Height -}} height="{{ . }}"{{- end -}}
            /> <!-- Closing img tag -->
        </picture>
        {{ end }}
        {{- if .Get "link" }}</a>{{ end -}}
        {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
            <figcaption>
                {{ with (.Get "title") -}}
                    {{ . }}
                {{- end -}}
                {{- if or (.Get "caption") (.Get "attr") -}}<p>
                    {{- .Get "caption" | markdownify -}}
                    {{- with .Get "attrlink" }}
                        <a href="{{ . }}">
                    {{- end -}}
                    {{- .Get "attr" | markdownify -}}
                    {{- if .Get "attrlink" }}</a>{{ end }}</p>
                {{- end }}
            </figcaption>
        {{- end }}
    </figure>
    

使用效果

正常插入jpg/png图片,构建后会自动生成webp/原始格式下不同规格的图片。

  • markdown图片显示

render-image

  • figure短代码显示

figure

  • 封面显示

cover

提示:

随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:

Start building sites  
hugo v0.96.0+extended darwin/arm64 BuildDate=unknown
Error: Error building site: "/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1": timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.
Built in 22356 ms

可以通过修改配置文件config.yml,新增timeout配置,调大超时时间解决。

buildDrafts: false
buildFuture: false
buildExpired: false

timeout: 60s // 调大此处的时间即可

终于知道为啥PagerMod主题默认只有封面下才有生成不同规格的逻辑了。