Optymalizacja obrazów

Wrzuć obraz w pełnej rozdzielczości do src/ albo public/, wskaż go znacznikiem <img>, a sitelo przeskaluje go, przekonwertuje do nowoczesnego formatu i przepisze znacznik z srcset. Nie ma czego importować ani żadnego komponentu do nauczenia.

Włączenie

export default {
  images: true,
}

Kodowaniem zajmuje się sharp, opcjonalna peer dependency — zainstaluj ją obok sitelo, gdy włączasz obrazy:

npm install -D sharp

Witryny, które rezygnują z optymalizacji obrazów, nigdy nie muszą jej instalować.

Napisz zwykły <img>

import { html, body, img } from 'javascript-to-html'

export default () =>
  html({ lang: 'pl' },
    body(
      img({ src: '/images/hero.png', alt: 'Wschód słońca nad portem' }),
    ),
  )

Źródło 3000×2000 wychodzi z drugiej strony tak:

<img src="/assets/img/hero.a1b2c3d4-3000.webp"
     alt="Wschód słońca nad portem"
     sizes="100vw"
     width="3000" height="2000"
     loading="lazy" decoding="async"
     srcset="/assets/img/hero.9f8e7d6c-400.webp 400w,
             /assets/img/hero.5b4a3c2d-800.webp 800w,
             /assets/img/hero.7c6d5e4f-1200.webp 1200w,
             /assets/img/hero.a1b2c3d4-3000.webp 3000w">

Ekran szeroki do 400px dostaje plik 400, do 800 — plik 800, do 1200 — plik 1200, a wszystko szersze pełne 3000: przeglądarka wybiera pierwszy szczebel pokrywający widoczny obszar (podwojony na ekranie 2×).

Co dostajesz

Przepisywanie działa na zbudowanym HTML-u, więc obejmuje tak samo obrazy z src/, jak i z public/.

Gdy znacznik zostanie przepisany, nic już nie wskazuje na oryginał, więc domyślnie znika on z buildu. Odchodzą tylko pliki naprawdę bez odwołań: każdy plik HTML, CSS, JS, XML i JSON w buildzie jest przeszukiwany, więc oryginał podlinkowany z <a href>, z og:image, z załącznika RSS, z url() w CSS-ie albo ze znacznika data-no-optimize zostaje. Adresów składanych w czasie działania skryptu albo renderowanych przez wyspę serwerową nie zobaczy — ustaw prune: false, jeśli takie masz.

Opcje

export default {
  images: {
    widths: [400, 800, 1200],
    formats: ['avif', 'webp'],
    quality: { avif: 55, webp: 78, jpeg: 82 },
    exclude: ['**/og/**'],
  },
}

Dwa formaty dają

AVIF jest mniejszy, ale młodszy od WebP, więc wypisanie obu pozwala przeglądarce wybrać i zostawia zapas dla starszych:

<picture>
  <source type="image/avif" srcset="/assets/img/hero.*-400.avif 400w, ..." sizes="...">
  <source type="image/webp" srcset="/assets/img/hero.*-400.webp 400w, ..." sizes="...">
  <img src="/assets/img/hero.*-3000.png" alt="..." srcset="..." width="3000" height="2000">
</picture>

Jeden rozmiar, po nazwie

Miniatury karuzeli, awatary, siatka kart — niektóre obrazy pokazują się zawsze w jednym rozmiarze i cała drabinka jest dla nich marnotrawstwem. Nazwy plików wariantów zawierają skrót treści, więc nie napiszesz ich ręcznie; zamiast tego podaj szerokość w adresie, a znacznik wyjdzie z tym jednym plikiem:

<img src="/images/hero.png?w=400" alt="Wschód słońca nad portem">
<img src="/images/hero.png?w=400&format=jpeg" alt="Wschód słońca nad portem">
<img src="/images/hero.png?w=200&h=200" alt="Wschód słońca nad portem">
<img src="/images/hero.png?w=200&h=200&fit=contain" alt="Wschód słońca nad portem">
<img src="/images/hero.png?w=200&h=200&background=fff" alt="Wschód słońca nad portem">
<img src="/images/hero.png?w=200&h=200&position=top" alt="Wschód słońca nad portem">
<img src="/assets/img/hero.9f8e7d6c-400.webp"
     alt="Wschód słońca nad portem"
     width="400" height="267"
     loading="lazy" decoding="async">

Przy kilku skonfigurowanych formats ustalona szerokość nadal daje <picture>, z jednym plikiem na <source>. Nazwy są te, których używa vite-imagetools. Zdalne adresy nigdy nie są parsowane — ich query string należy do źródła — a przy wyłączonym images hostingi statyczne ignorują zapytanie i serwują oryginał.

Niech zdjęcie samo wybierze kadr

Krawędź albo róg są przewidywalne, ale karuzela różnych zdjęć rzadko ma temat dwa razy w tym samym miejscu. entropy i attention każą sharpowi spojrzeć na każde zdjęcie i przesunąć okno kadru tam, gdzie znajdzie coś wartego zachowania:

<img src="/images/hero.png?w=300&h=300&position=entropy" alt="Wschód słońca nad portem">
<img src="/images/team.jpg?w=300&h=300&position=attention" alt="Zespół">

Obie to zwykłe heurystyki — bez modelu i bez uczenia — więc traktuj je jak rozsądne ustawienie domyślne dla wielu obrazów, a nie gwarancję dla jednego. Dla obrazu otwierającego, na którym Ci zależy, wskaż krawędź sam. Wybór zależy wyłącznie od zdjęcia, więc jest deterministyczny i buforuje się jak każdy inny wariant.

Rezygnacja

Znaczniki, które już mają srcset, siedzą wewnątrz <picture> albo wskazują na SVG, animowanego GIF-a lub adres zdalny, zostają nietknięte. Dla wszystkiego innego powiedz to wprost:

<img src="/images/exact.png" alt="Pixel art" data-no-optimize>

Obrazy kart społecznościowych i favikony żyją w <meta> i <link>, których to nigdy nie przepisuje — zachowują swój stały adres.

Obrazy zdalne

Treść zaciągnięta z CMS-a często wskazuje na cudzy serwer. Włącz remote, a te obrazy zostaną pobrane, zoptymalizowane i będą serwowane z Twojej domeny:

export default {
  images: {
    remote: true,
  },
}

Nieudane pobranie zostawia znacznik dokładnie takim, jaki był, z ostrzeżeniem — kapryśne źródło nigdy nie przerwie Twojego buildu. prune usuwa potem lokalne oryginały, do których nic się już nie odwołuje.