Одной из вещей, с которой надо было разобраться при настройке блога на Hugo - это публикация картинок в оптимизированном виде. Чтобы это сделать потребовалось окунуться в нюансы фронтэнд-разработки и верстку, а также разобраться с тем как браузер грузит картинки. Только после этого удалось всё настроить как я хочу.


Со времен первых сайтов в html-разметке страниц делают теги, которые показывают браузеру в каком месте отобразить картинку, какой ширины ее сделать, а также указатель на сам файл изображения. Этих параметров было достаточно.

Всё было просто пока все сидели в интернете только с компов. У всех были одинаковые мониторы с одинаковым разрешением. Сайты было делать легко: создаёшь один единственный макет под типовой размер экрана и всё - можно быть уверенным, что он отобразится у всех одинаково.

Но потом появились телефоны, планшеты, которые могут быть расположены горизонтально. Это значит, что окно просмотра страницы у всех стало разное, появились разные размеры мониторов, разные разрешения.

А еще поменялся интернет, с которого мы открываем сайты. С мобильного интернета на телефоне сайты открываются медленнее.

Всё это повлияло на разработку сайтов. Сайты стали проектировать с учетом разной ширины экранов у посетителей и брать во внимание, что процесс загрузки страницы не моментальный из-за местами медленного мобильного интернета.

Быстро загружаемый сайт с читабельным дизайном на любом устройстве приятно посещать. Такие сайты нравятся посетителям, и, следовательно, и поисковикам, которые стали поднимать в выдаче оптимизированные сайты выше остальных. Появилась “резиновая” вёрстка: когда один и тот же сайт стал отображаться по разному на десктопе и телефоне. Блоки, из которых состоит веб-страница, стали отображаться по разному в зависимости от ширины экрана.

Один из примеров - это отображение двух колонок текста. На десктопе они будут отображаться как две колонки, горизонтально распложенные друг с другом, а на мобиле места на экране для двух колонок не будет, поэтому каждая колонка будет отображаться вертикально друг за другом для читабельности на узком экране.

Веб-разработка усложнилась, чтобы соответствовать времени.

Качественный сайт должен грузиться быстро и картинки на нём должны быть оптимизированы. Что это значит?

Что такое оптимизированные изображения?

Оптимизированные изображения:

  • занимают мало места;
  • для них прописаны параметры в alt, это нравится поисковикам;
  • они выложены в современном формате, которые хорошо сжимает картинку;
    • Я знал, что PNG лучше JPG, но оказывается технологии сжатия картинок ушли вперед и был придуман еще один формат - WebP, который сжимает на 30% (!) лучше чем Jpeg. Этот формат придумали в Google ещё в 2010-м году.
  • файлы изображений на сервере имеют такой же размер как и размер блока, куда они должны будут вписаться на конечной странице. Это важно. Гугл, например, в своём тесте Lighthouse смотрит размер исходного изображения, смотрит размер изображения, которое вписано в сетку. Дальше сравнивает размер исходного изображения с вписанным в сетку. Если разница больше чем 4 килобайта, то Гугл считает это изображение не оптимизированным. Набрать 100 баллов в lighthouse тесте - значит убедиться, что сайт максимально оптимизирован и понравится поисковикам. Если на сайте оптимизированы изображения и остальные элементы, то на поисковые запросы сайт будет выдаваться выше. Этим и не только этим занимается SEO-оптимизация.

Кстати, JPG и Jpeg - это одно и то же. Просто раньше было ограничение на длину расширения файла в три символа.

Png не портит линии, хорош для скриншотов.

В нынешние времена владелец сайта должен генерировать несколько изображений разной ширины под каждый возможный экран посетителя: для маленького экрана, для экрана пошире и для самых больших экранов.

Браузер при загрузке страницы получает следующую информацию в теге, описывающем картинку:

при таком-то размере экрана картинка будет примерно следующего размера (параметр sizes. Здесь имеется в виду размер по ширине и высоте, не размер, занимаемый в килобайтах).

возьми подходящее изображение, на выбор из списка изображений srcset на своё усмотрение, учитывая текущий размер окна просмотра сайта в браузере.

Браузер при загрузке страницы сам понимает какое изображение взять: при доступе с компа и наличии быстрого интернета возьмёт самое большое изображение, потому что на компе быстрый интернет. При доступе с телефона и мобильного интернета выберет размером поменьше.

Поэтому при подготовке верстки сайта фронтэнд-разработчик думает над тем какие шаги ширины взять для генерации вариантов изображений одной картинки. Под какие размеры экранов?

  1. Можно взять стандартные градации: 320w, 640w, 960w, 1280w, 1920w и 2560w. Это — круглые цифры, получаемые путём умножения 320 на разные коэффициенты. Этот набор покроет нужды для самых разных экранов — от маленьких мобильных дисплеев, до огромных настольных мониторов.

  2. Но есть способ лучше - посмотреть дизайн своего проекта и понять какие размеры используются в сетке, держа в уме, что они могут открываться также с экранов с высокой плотностью пикселей (обычно x2).

У меня картинка вписывается в колонку с текстом по ширине 720px. Если размер окна браузера растянуть шире чем 720px, то все равно согласно текущим настройкам стилей сайта колонка с текстом останется шириной равной 720px, она будет отображаться по центру экрана, а по бокам будет увеличиваться свободное пространство. Поэтому мне нет смысла делать размер изображений больше чем 720 для текущего шаблона темы PaperMod. Возможно, только если взять ещё одну ширину в 720*2 = 1440px для отображения на высокочетких дисплеях, у которых плотность пикселей выше. А вот подготовить сгенерированные изображения размером поменьше - смысл есть, чтобы браузер клиента мог из них себе подобрать что-нибудь при загрузке страниц, например, с телефона с вертикальным экраном.

До того, как я начал делать на этом сайте оптимизацию изображений - они вставлялись в колонку с текстом с оригинальным размером, просто сам браузер подгонял изображение под размер блока текста в 720w, исходный размер изображения оставался. А если в посте было несколько таких картинок, то размер страницы набегал под десятки мегабайт. Каждое изображение доходило до 8 мб в оригинале.

Пример, когда вставлено необработанное изображение в код:

Поэтому я и начал с этой темой разбираться. Захотел сделать сайт красиво и правильно. Я пользуюсь движком статических сайтов Hugo, темой PaperMod. Здесь изначально оптимизации не было никакой. Поэтому я пошёл искать дальше.

Нашел блог Брюса, в котором он поделился своими наработками как сделать резиновые изображения в блоге на основе Hugo. Там я брал информацию за основу и применял здесь.

https://www.brycewray.com/posts/2022/06/responsive-optimized-images-hugo


Есть три ключевых элемента.

head-imgs-css.html

partial template для head.html. Этот шаблон вызывается из head.html шаблона.

Ищет картинки в page bundle и создаёт LQIP и GIP. Это вроде как плейсхолдеры для будущих картинок, которые будут подгружены.

Лежит здесь:

>\themes\PaperMod\layouts\partials\templates\

Текст его такой:

{{- with .Resources.ByType "image" }}
	<style media="screen">
	{{- range . -}}
		{{- $src := . -}}
		{{- $imgBd5 := md5 .Name -}}
		{{- $BkgdStyleEnd := print " center / cover no-repeat; aspect-ratio: " $src.Width " / " $src.Height ";" -}}
		{{- $GIP_img := $src.Process "resize 20x webp q20" -}}
		{{- /* ^^ documentation says we get better performance by shrinking first */ -}}
		{{- $GIP_colors := $GIP_img.Colors -}}
		{{- if (lt ($GIP_colors | len) 2) -}}
			{{- $GIP_colors = $GIP_colors | append "#000000" -}}
		{{- end -}}
		{{- $GIP_bkgd := delimit ($GIP_colors) ", " -}}
		{{- $BkgdStyleGIP := print "background: linear-gradient(" $GIP_bkgd ")" $BkgdStyleEnd -}}
		{{- $LQIP_img := $src.Process "resize 20x webp q20" -}}
		{{- $LQIP_b64 := $LQIP_img.Content | base64Encode -}}
		{{- $BkgdStyleLQIP := print "background: url(data:image/webp;base64," $LQIP_b64 ")" $BkgdStyleEnd }}
		.imgB-{{ $imgBd5 }}-GIP {
			{{ $BkgdStyleGIP | safeCSS }}
		}
		.imgB-{{ $imgBd5 }}-LQIP {
			{{ $BkgdStyleLQIP | safeCSS }}
		}
	{{- end }}
	</style>
{{ end }}

Попробовал сделать и добавил вызов этого куска в head.html, который лежит в

>\themes\PaperMod\layouts\partials\head.html
{{- partial "extend_head.html" . -}}
{{- template "partials/templates/head-imgs-css.html" . }}

После этого внутри html-страниц появились ссылки на LQIP и GIP на каждую картинку, чтобы это не значило.

Потом я откатил изменения, потому что пока не понял зачем мне это.

render-image.html

  • код для генерации и сжатия изображений под заданные градации.

Здесь генерируются два изображения JPEG и WEBP под разные градации ширины.

Нужно положить в:

>\themes\PaperMod\layouts\_default\_markup\render-image.html

Здесь у него три параметра:

  • the image’s file name ($src)
  • image’s alt text ($alt)
  • (optional) title parameter which, in my case, I mix with certain CSS to provide a caption for the image

заменил код, сразу время создания сайта вылезло большое и картинки стали вытянутыми по вертикали.

подменил две строчки:

42 строчка я убрал упоминанию про Height:

<img class="{{ $imgClass }}" src="{{ $actualImg.RelPermalink }}" width="{{ $imgRsc.Width }}" alt="{{ $alt }}" title="{{ $alt }}" loading="lazy" data-pagefind-ignore />

Width убирать нельзя, иначе картинка становится меньше чем ширина текста при изменении размера окна. Но если картинка маленькая, то её не увеличивает. Но в этой строчке еще было про Height и я это убрал - стало нормально.

Когда я пробовал эти советы в Hugo Extended версии 0.127, то параметры сформированной картинки перестали отображаться в сгенерированном оптимизированном изображении. У них теперь имя вроде такого file-name_hu_aa8e3eaad71de001.webp

Раньше в версии до 0.127 при генерации изображения для финального набора html-страниц в папке \public включалось имя файла в название файла с изображением:

file-name_hu05e97a39bb1cba49f6068d5eece33689_2325967_400x0_resize_q90_h2_lanczos.webp

У меня так сейчас стоит:

 1{{- /*
 2	This gives **normal** Markdown images --- i.e., using the `![Alt](imageURL)` syntax (which VS Code auto-completes) --- nearly all functionality of the `img` shortcode, except that this: (a.) hard-codes the `holder`, `hint`, and `filter` parameters; and, (b.) assumes no use of either `phn` or `simple`. None of these additional items can be supplied via a render hook but, instead, would require use of the full `img` shortcode.
 3
 4	See also:
 5	- https://gohugo.io/templates/render-hooks/
 6	- https://discourse.gohugo.io/t/get-a-remote-resource-with-its-url-defined-in-page-frontmatter/41690
 7*/ -}}
 8{{- $respSizes := slice "390" "720" -}}
 9{{- $alt := .Text -}}
10{{- $caption := .Title -}}
11{{- $src := .Destination -}}
12{{- $imgBd5 := md5 $src -}}
13{{- $dataSzes := "(max-width: 720px) 100vw, 720px" -}}
14{{- $holder := "GIP" -}}
15{{- $hint := "photo" -}}
16{{- $filter := "Lanczos" -}}
17{{- $imgClass := "w-auto h-auto shadow animate-fade" -}}
18
19{{- if .Page.Resources.GetMatch $src -}}
20	{{ with .Page.Resources.GetMatch $src }}
21		{{- $divClass := print "relative bigImgDiv imgB-" $imgBd5 "-" $holder -}}
22		{{- $imgRsc := . -}}
23		{{- $actualImg := $imgRsc.Process (print "resize 640x jpg " $filter) -}}
24		<div class="{{ $divClass }}" data-pagefind-ignore>
25			<picture data-pagefind-ignore>
26				<source type="image/webp" srcset="
27				{{- with $respSizes -}}
28					{{- range $i, $e := . -}}
29						{{- if ge $imgRsc.Width . -}}
30							{{- if $i }}, {{ end -}}{{- ($imgRsc.Process (print "resize " . "x webp " $hint " " $filter) ).RelPermalink }} {{ . }}w
31						{{- end -}}
32					{{- end -}}
33				{{- end -}}" sizes="{{ $dataSzes }}" />
34				<source type="image/jpeg" srcset="
35				{{- with $respSizes -}}
36					{{- range $i, $e := . -}}
37						{{- if ge $imgRsc.Width . -}}
38							{{- if $i }}, {{ end -}}{{- ($imgRsc.Process (print "resize " . "x jpg " $filter) ).RelPermalink }} {{ . }}w
39						{{- end -}}
40					{{- end -}}
41				{{- end -}}" sizes="{{ $dataSzes }}" />
42				<img class="{{ $imgClass }}" src="{{ $actualImg.RelPermalink }}" width="{{ $imgRsc.Width }}" alt="{{ $alt }}" title="{{ $alt }}" loading="lazy" data-pagefind-ignore />
43			</picture>
44		</div>
45	{{- end -}}
46{{- else -}}
47	<p class="ctr legal"><em>Image unavailable.</em></p>
48{{- end -}}
49{{- with $caption -}}<p class="img-caption">{{ $caption | $.Page.RenderString }}</p>{{- end }}

img.html

  • shortcode, используется, когда нужно больше параметров для написания кода вставки картинки в markdown или близкок нему, чтобы отображалась подпись к картинке. В теме PaperMod не отображается подпись, потому что, думаю, это отображение не описано в CSS этой темы. Пока не делал. Работает без этой штуки.

Нюанс с кэшем при тесте

Когда тестируешь локально какой файл с изображением браузер подставляет в финальную страницу, то нужно отключать кэш:

Edge -> F12 открывает Developer Tools. То, какие файлы используются при загрузке страницы можно посмотреть на вкладке Network. И в верхнем меню надо активировать Disable Cache.

Developer-tools-Edge активирована опция Disable Cache

дополнительные ссылки на будущее

  1. https://developer.mozilla.org/ru/docs/Web/Performance/Guides/How_browsers_work
  2. https://imagekit.io/responsive-images/#chapter-10---how-to-verify-responsive-image-implementation
  3. https://mertbakir.gitlab.io/hugo/image-processing-in-hugo/