Optymalizacja obrazów
Na tej stronie
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 sharpWitryny, które rezygnują z optymalizacji obrazów, nigdy nie muszą jej instalować.
Napisz zwykły <img>
export default () => `
<html lang="pl">
<body>
<img src="/images/hero.png" alt="Wschód słońca nad portem">
</body>
</html>
`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' }),
),
)export default function Home() {
return (
<html lang="pl">
<body>
<img src="/images/hero.png" alt="Wschód słońca nad portem" />
</body>
</html>
)
}Ź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
- Skalowanie, które nigdy nie powiększa — każda skonfigurowana szerokość mniejsza od źródłowej, a na szczycie własna szerokość źródła. Źródło 750px przy
widths: [400, 800, 1200]daje 400 i 750, i na tym koniec. - Nowoczesne formaty — jeden format daje zwykły
<img srcset>; dwa albo więcej owijają go w<picture>z zapasem w formacie oryginalnym. - Brak przeskoków układu —
widthiheightsą uzupełniane z prawdziwego obrazu, plusloading="lazy"idecoding="async". - Te same znaczniki w dev —
sitelo(dev) przepisuje strony i serwuje warianty na żądanie, więc oglądasz to, co opublikujesz. - Wspólny cache — warianty są kluczowane skrótem treści w
node_modules/.sitelo/images, więc dev i kolejne buildy nigdy nie kodują tego samego obrazu dwa razy.
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/**'],
},
}widths— domyślnie[400, 800, 1200]; własna szerokość źródła zawsze wieńczy drabinkęformats— domyślnie['webp'];avif,webp,jpeg,pngquality— domyślnie{ avif: 55, webp: 78, jpeg: 82 }; jakość kodera na format (png używa kompresji, nie jakości)sizes— atrybutsizes;sizesna znaczniku zawsze wygrywadimensions— domyślnietrue; dodajewidth/heightlazy— domyślnietrue; dodajeloadingidecodingexclude— glob(y) albo wyrażenia regularne adresów obrazów, których nie ruszaćassetsDir— domyślnie'assets/img'cacheDir— domyślnie'node_modules/.sitelo/images'remote— domyślniefalse; optymalizuj obrazyhttps://podczas builduprune— domyślnietrue; usuwaj oryginały, do których nic w buildzie już się nie odwołujedev— domyślnietrue; ustawfalse, by w dev serwować nietknięte oryginałyconcurrency— równoległe kodowania, domyślnie CPU − 1 (maks. 8); 1, gdy remote jest włączone
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">w— szerokość w pikselach. Działa każda szerokość, nie tylko te zwidths; mniejsze źródło zachowuje swój rozmiar zamiast być powiększane.h— wysokość w pikselach. Samo w sobie ustala szerokość zgodnie z proporcjami; razem zwobraz jest skalowany tak, by wypełnić ramkę, i do niej przycinany —?w=200&h=200to kwadratowa miniatura.fit— jak wypełniana jest ramkaw×h:cover(domyślnie) wypełnia i przycina;containskaluje cały obraz tak, by się w niej zmieścił, bez przycinania i bez marginesów —width/heightmówią, co wyszło.background— dopełnia wynikcontaindo dokładnej ramki i implikujefit=contain. Szesnastkowo bez#(fff,1a1a1a,ffffff80), nazwa koloru CSS albotransparent— ten wymaga formatu z kanałem alfa; na jpeg wychodzi czarny.position— którą część zachowuje przycięciecover. Domyślnie środek; krawędź albo róg (top,left,right-bottom, …), albo świadome treści strategie sharpaentropy/attention.format—avif,webp,jpegalbopng. Samo zachowuje pełną drabinkę, w tym jednym formacie.
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ół">entropyzachowuje obszar z największą ilością szczegółu — krawędzie, fakturę, zmienność koloru. Płaskie niebo, puste ściany i jednolite tła odchodzą pierwsze. Dobre do krajobrazów, produktów i wszystkiego, gdzie „gęsto” znaczy „ciekawie”.attentionprzechyla kadr w stronę nasyconych kolorów, wysokiej częstotliwości jasności i odcieni skóry — to heurystyka tego, gdzie spojrzałby człowiek. Dobre do zdjęć ludzi: zwykle trafia w twarze i postaci.
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.