Automatyzacja kompresji i kodowania

Generowanie bardzo wydajnych źródeł obrazów powinno być płynnym elementem procesu programowania.

Wszystkie składnie użyte w tym kursie – od kodowania danych o obrazie po znaczniki z dużą ilością informacji, które stanowią podstawę obrazów elastycznych – to metody komunikacji między maszynami. Udało Ci się odkryć wiele sposobów, w jakie przeglądarka kliencka komunikuje swoje potrzeby z serwerem, a serwer może odpowiadać w naturze. Elastyczne znaczniki obrazów (zwłaszcza srcset i sizes) pozwalają za pomocą kilku znaków opisywać szokujące informacje. Zdecydowanie zwięzłość jest taka, że zwięzłość jest z założenia bardziej zwięzła: skrócenie tych składni byłoby mniej zwięzłe i łatwe do przeanalizowania przez programistów, co mogłoby utrudnić przeglądarce analizę. Im większa złożoność ciągu, tym większe potencjalne błędy parsera lub niezamierzone różnice w działaniu między przeglądarkami.

Okno automatycznego kodowania obrazu.

Jednak ta sama cecha, która może sprawiać, że tak onieśmiela te osoby, może też podpowiadać rozwiązania: składnia, którą komputery mogą łatwo odczytać, jest o nią łatwiej napisana. Na pewno wielu użytkowników internetu korzysta z automatycznego kodowania i kompresji obrazów. Każdy obraz przesłany do internetu za pomocą platform mediów społecznościowych, systemów zarządzania treścią (CMS), a nawet klienty poczty e-mail jest niemal niezmiennie przekazywany przez system, który zmienia ich rozmiar, ponownie koduje i kompresuje.

Podobnie, czy to za pomocą wtyczek, bibliotek zewnętrznych, samodzielnych narzędzi do procesu tworzenia, czy odpowiedzialnego korzystania ze skryptów po stronie klienta, elastyczne znaczniki obrazów łatwo nadają się do automatyzacji.

To 2 główne kwestie związane z automatyzacją wydajności obrazów: zarządzaniem tworzeniem obrazów – ich kodowaniem i kompresją, a także alternatywnymi źródłami służącymi do wypełniania atrybutu srcset oraz generowaniem znaczników widocznych dla użytkowników. W tym module poznasz kilka typowych metod zarządzania obrazami w ramach nowoczesnego procesu tworzenia, czy to w ramach automatycznej fazy procesu tworzenia, przez platformę lub system zarządzania treścią, które są wykorzystywane w Twojej witrynie, czy przez system zarządzania treścią, który jest wykorzystywany w Twojej witrynie, czy też w sposób całkowicie odseparowany przez dedykowaną sieć dostarczania treści.

Automatyzacja kompresji i kodowania

Raczej mało prawdopodobne jest, że znajdziesz się w sytuacji, w której możesz ręcznie określić idealne kodowanie i poziom kompresji dla każdego obrazu przeznaczonego do użycia w danym projekcie. Bardzo ważne jest , by rozmiar transferu obrazu był jak najmniejszy. Dopracowanie ustawień kompresji i ponowne zapisywanie alternatywnych źródeł każdego zasobu graficznego wykorzystywanego w witrynie produkcyjnej znacznie utrudniłoby codzienną pracę.

Poczytaj o różnych formatach obrazów i typach kompresji, dowiesz się, że najbardziej efektywne kodowanie obrazu zawsze będzie zależeć od jego zawartości. Jak dowiesz się z sekcji Obrazy elastyczne, alternatywne rozmiary źródeł obrazów będą zależeć od pozycji, jaką obrazy zajmują w układzie strony. W nowoczesnym przepływie pracy do takich decyzji podejmujesz się pojedynczo, a nie pojedynczo. Ustalając zestaw rozsądnych ustawień domyślnych dla obrazów, które najlepiej pasują do kontekstów, w których mają być używane.

Przy wyborze kodowania dla katalogu zdjęć fotograficznych AVIF zdecydowanie wygrywa pod względem jakości i rozmiaru przenoszenia, ale ma ograniczoną obsługę. WebP zapewnia zoptymalizowany, nowoczesny plik zastępczy, a JPEG jest najpewniejszym ustawieniem domyślnym. Rozmiary alternatywne, które tworzymy, na potrzeby obrazów, które zajmują pasek boczny w układzie strony, różnią się znacznie od obrazów zajmujących cały widoczny obszar przeglądarki w najwyższych punktach przerwania. Ustawienia kompresji będą wymagały uwzględniania zamazań i artefaktów kompresji w wielu plikach wynikowych, co pozwala oszczędzić miejsce w każdym bajciem w zamian za bardziej elastyczny i niezawodny przepływ pracy. Podsumowując, będziesz kontynuować proces decyzyjny taki sam jak w tym kursie.

Jeśli chodzi o proces przetwarzania, istnieje ogromna liczba bibliotek open source do przetwarzania obrazów, które zapewniają metody przetwarzania zbiorczego, modyfikowania i edytowania obrazów, konkurując pod względem szybkości, wydajności i niezawodności. Biblioteki te umożliwiają stosowanie ustawień kodowania i kompresji w całych katalogach zdjęć jednocześnie, bez konieczności otwierania oprogramowania do edycji obrazów. Pozwala też zachować oryginalne źródła obrazów, jeśli trzeba zmienić te ustawienia na bieżąco. Narzędzia te działają w różnych kontekstach, od lokalnego środowiska programistycznego po sam serwer WWW. Na przykład skoncentrowany na kompresji pakiet ImageMin dla Node.js można rozszerzyć do konkretnych aplikacji za pomocą tablicy wtyczek, podczas gdy wieloplatformowe ImageMagick i Sharp oparte na Node.js udostępniają ogromnie różne funkcje.

Te biblioteki przetwarzania obrazów umożliwiają programistom tworzenie narzędzi dedykowanych płynnej optymalizacji obrazów w ramach standardowych procesów programistycznych. Dzięki temu projekt zawsze odwołuje się do źródeł obrazów gotowych do produkcji przy jak minimalnym nakładzie pracy.

Lokalne narzędzia dla programistów i przepływy pracy

Programy uruchamiające zadania i programy do tworzenia pakietów, takie jak Grunt, Gulp czy Webpack, mogą służyć do optymalizacji zasobów graficznych oraz innych typowych zadań związanych ze skutecznością, takich jak minifikacja CSS i JavaScript. W celu zilustrowania tego przykładu wykorzystamy względnie prosty przypadek użycia: katalog w projekcie zawiera kilkanaście zdjęć fotograficznych przeznaczonych do użytku w publicznej witrynie.

Po pierwsze musisz zadbać o spójne i efektywne kodowanie tych obrazów. Jak wiesz z poprzednich modułów, WebP to wydajne rozwiązanie domyślne do obsługi zdjęć fotograficznych pod względem jakości i rozmiaru pliku. Format WebP jest dobrze obsługiwany, ale nie uniwersalnie obsługiwany, dlatego warto też dodać plik zastępczy w postaci progresywnego pliku JPEG. Następnie, aby wykorzystać atrybut srcset do skutecznego przesyłania tych zasobów, musisz utworzyć wiele rozmiarów alternatywnych dla każdego kodowania.

Choć w przypadku oprogramowania do edycji obrazów byłoby to powtarzalne i czasochłonne czynności, osoby uruchamiające zadania takie jak Gulp zostały zaprojektowane w taki sposób, aby automatyzować takie powtórzenia. Wtyczka gulp-responsive, która korzysta z narzędzia Sharp, jest jedną z wielu, a wszystkie działają zgodnie z podobnym wzorcem: gromadzą wszystkie pliki w katalogu źródłowym, kodują je i kompresują, korzystając z tego samego ustandaryzowanego skrótu „jakości” znanego z artykułu Formaty obrazów i kompresja. Uzyskane pliki są następnie przesyłane do zdefiniowanej przez Ciebie ścieżki. Można się do nich odwoływać w atrybutach src elementów img widocznych dla użytkowników, pozostawiając przy tym oryginalne pliki bez zmian.

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.webp = function() {
  return src('./src-img/*')
    .pipe(respimg({
      '*': [{
        quality: 70,
        format: ['webp', 'jpeg'],
        progressive: true
      }]
  }))
  .pipe(dest('./img/'));
}

Dzięki takiemu procesowi nie szkodzi środowisku produkcyjnemu, jeśli ktoś w projekcie nieumyślnie dodał do katalogu zawierającego oryginalne źródła obrazów zdjęcie zakodowane w formacie masywnego formatu truecolor PNG. Niezależnie od kodowania obrazu oryginalne zadanie pozwala uzyskać wydajną technologię WebP i niezawodną zastępczą progresywną plik JPEG z możliwością łatwej modyfikacji na bieżąco. Ten proces zapewnia również, że oryginalne pliki obrazów zostaną zachowane w środowisku programistycznym projektu, co oznacza, że te ustawienia można zmienić w dowolnym momencie i zastąpić tylko zautomatyzowane dane wyjściowe.

Aby wygenerować wiele plików, należy przekazać wiele obiektów konfiguracji – takich samych, z wyjątkiem dodania klucza width i wartości w pikselach:

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.default = function() {
  return src('./src-img/*')
    .pipe(respimg({
    '*': [{
            width: 1000,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-1000' }
            },
            {
            width: 800,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-800' }
            },
            {
            width: 400,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-400' },
        }]
        })
    )
    .pipe(dest('./img/'));
}

W powyższym przykładzie oryginalny obraz (monarch.png) miał więcej niż 3, 3 MB. Największy plik wygenerowany przez to zadanie (monarch-1000.jpeg) ma około 150 KB. Najmniejszy plik, monarch-400.web, waży zaledwie 32 KB.

[10:30:54] Starting 'default'...
[10:30:54] gulp-responsive: monarch.png -> monarch-400.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-800.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-400.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-800.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.webp
[10:30:54] gulp-responsive: Created 6 images (matched 1 of 1 image)
[10:30:54] Finished 'default' after 374 ms

Oczywiście warto dokładnie zbadać wyniki pod kątem widocznych artefaktów kompresji lub zwiększyć kompresję, aby uzyskać dodatkowe oszczędności. Nie powoduje to szkód, dlatego te ustawienia można łatwo zmienić.

W zamian za kilka kilobajtów, które można by przeznaczyć na dokładną ręczną mikrooptymalizację, zyskujesz proces, który jest nie tylko wydajny, ale też odporny, czyli narzędzie, które płynnie wykorzystuje Twoją wiedzę o wysokiej wydajności zasobów graficznych do całego projektu bez konieczności ręcznej interwencji.

Elastyczne znaczniki obrazu w praktyce

Wypełnianie atrybutów srcset zwykle odbywa się w prosty sposób ręcznie, ponieważ rejestruje on tylko informacje o konfiguracji dokonanej już podczas generowania źródeł. W powyższych zadaniach określiliśmy nazwy plików i informacje o szerokości, z którymi będzie korzystać nasz atrybut:

srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w"

Pamiętaj, że zawartość atrybutu srcset ma charakter opisowy, a nie nakazowy. Zbyt przeciążenie atrybutu srcset nic nie szkodzi, o ile współczynnik proporcji każdego źródła jest taki sam. Atrybut srcset może zawierać identyfikator URI i szerokość każdego alternatywnego cięcia wygenerowanego przez serwer, nie powodując niepotrzebnych żądań. Im więcej źródeł poprawimy dla wyrenderowanego obrazu, tym skuteczniej przeglądarka będzie w stanie dostosować żądania.

Jak wiesz z sekcji Obrazy elastyczne, musisz używać elementu <picture>, aby płynnie obsługiwać wzorzec kreacji zastępczej WebP lub JPEG. W takim przypadku atrybut type będzie używany razem z atrybutem srcset.

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

Jak już wiesz, przeglądarki obsługujące WebP rozpoznają zawartość atrybutu type i wybiorą atrybut srcset elementu <source> jako listę kandydujących obrazów. Przeglądarki, które nie rozpoznają typu mediów image/webp jako prawidłowego typu mediów, ignorują tag <source>, a zamiast tego korzystają z atrybutu srcset wewnętrznego elementu <img>.

Jeśli chodzi o obsługę przeglądarek, trzeba wziąć pod uwagę jeszcze jedną kwestię: przeglądarki, które nie obsługują żadnych elastycznych znaczników obrazów, będą nadal potrzebować opcji zastępczych, ponieważ w szczególności starszych kontekstów przeglądania możemy ryzyko uszkodzonego obrazu. Ponieważ <picture>, <source> i srcset są ignorowane w tych przeglądarkach, źródło domyślne trzeba określić w atrybucie src wewnętrznego tagu <img>.

Ponieważ skalowanie obrazu w dół jest wizualne bezproblemowe, a kodowanie JPEG jest powszechnie obsługiwane, najlepszym rozwiązaniem będzie wybór największego pliku JPEG.

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img src="filename-1000.jpg" srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

Obsługa sizes może być nieco trudniejsza. Jak wiesz, atrybut sizes ma koniecznie kontekst kontekstowy – nie możesz wypełnić atrybutu, jeśli nie wiesz, ile miejsca obraz ma zająć w renderowanym układzie. Aby żądania były jak najwydajniejsze, w naszych znacznikach powinien znajdować się dokładny atrybut sizes w chwili wysyłania takich żądań przez użytkownika, na długo przed zażądaniem stylów zarządzających układem strony. Pominięcie elementu sizes nie tylko narusza specyfikację HTML, ale powoduje też domyślne zachowanie zgodne z zasadą sizes="100vw". Informuje przeglądarkę, że obraz jest ograniczony tylko przez widoczny obszar, co skutkuje wybraniem największych możliwych źródeł.

Tak jak w przypadku niektórych szczególnie uciążliwych zadań związanych z tworzeniem stron internetowych, stworzono wiele narzędzi w celu wyodrębnienia procesu pisania odręcznego w atrybutach sizes. respImageLint to absolutnie niezbędny fragment kodu, który służy do sprawdzania dokładności atrybutów sizes i dostarczania sugestii dotyczących ich ulepszeń. Działa jako skryptozakładki – narzędzie, które uruchamia się w przeglądarce i wskazuje w pełni wyrenderowaną stronę z elementami graficznymi. W sytuacjach, gdy przeglądarka w pełni rozumie układ strony, będzie miała niemal idealną wiedzę o obszarze zajmowanym przez obraz w danym układzie przy każdym możliwym rozmiarze widocznego obszaru.

Raport o elastycznych obrazach przedstawiających niezgodność rozmiaru do szerokości ekranu.

Narzędzie do lintowania atrybutów sizes jest z pewnością przydatne, ale ma jeszcze większą wartość jako narzędzie do ich sprzedaży hurtowej. Jak wiesz, składnia srcset i sizes służy do optymalizowania żądań komponentów z obrazem w sposób przejrzysty. Choć nie należy używać takiej wartości w środowisku produkcyjnym, domyślna wartość zmiennej sizes w wysokości 100vw jest całkowicie rozsądna, gdy będziesz pracować nad układem strony w lokalnym środowisku programistycznym. Gdy użyjesz stylów układu, funkcja respImageLint wyświetli dostosowane atrybuty sizes, które możesz skopiować i wkleić do swoich znaczników. Poziom szczegółowości znacznie przekracza te napisane ręcznie:

Raport o elastycznych obrazach z sugerowanymi wymiarami.

Chociaż żądania obrazów zainicjowane przez znaczniki renderowane przez serwer występują zbyt szybko, by JavaScript mógł wygenerować atrybut sizes po stronie klienta, to samo uzasadnienie nie ma zastosowania, jeśli te żądania są zainicjowane po stronie klienta. Na przykład projekt Lazysizes umożliwia pełne odłożenie żądań obrazu do momentu utworzenia układu, dzięki czemu JavaScript może za nas generować wartości sizes, co jest ogromną wygodą dla użytkowników, a użytkownikom gwarantuje najwyższą efektywność. Pamiętaj jednak, że takie podejście oznacza utratę niezawodności znaczników renderowanych przez serwer i optymalizacji szybkości wbudowanej w przeglądarki oraz wysyłanie żądań dopiero po wyrenderowaniu strony, co będzie miało bardzo negatywny wpływ na wynik LCP.

Oczywiście, jeśli korzystasz już z platformy renderowania po stronie klienta, takiego jak React czy Vue, będziesz ponosić dług. W takich przypadkach używanie Lenisizes oznacza, że atrybuty sizes można niemal całkowicie wycofać. Co więcej: gdy działanie sizes="auto" w przypadku obrazów leniwie ładowanych i zyska konsensus i implementacje natywne, Lazysizes stanie się polyfill dla nowego ustandaryzowanego sposobu działania przeglądarki.