Tworzenie treści na potrzeby internetu daje Ci nieograniczony zasięg. Twoja aplikacja internetowa jest dostępna w jednym kliknięciu na prawie każdym urządzeniu podłączonym do internetu: smartfonie, tablecie, laptopie, komputerze stacjonarnym, telewizorze itp., niezależnie od marki czy platformy. Aby zapewnić użytkownikom jak najlepsze wrażenia, stworzyłeś witrynę responsywną, która dostosowuje prezentację i funkcjonalność do każdego formatu. Teraz sprawdzasz listę kontrolną dotyczącą wydajności, aby aplikacja wczytywała się tak szybko, jak to możliwe: zoptymalizowałeś krytyczną ścieżkę renderowania, skompresowałeś i zapisywałeś w pamięci podręcznej zasoby tekstowe, a teraz sprawdzasz zasoby graficzne, które często stanowią większość przesyłanych bajtów. Problem polega na tym, że optymalizacja obrazów jest trudna:
- Określ odpowiedni format (wektorowy lub rastrowy).
- Określ optymalne formaty kodowania (jpeg, webp itp.)
- Określ odpowiednie ustawienia kompresji (stratna lub bezstratna)
- Określanie, które metadane powinny zostać zachowane lub usunięte
- Utwórz kilka wariantów dla każdej rozdzielczości wyświetlacza i rozdzielczości DPR.
- …
- uwzględniać typ, szybkość i ustawienia sieci użytkownika;
Pojedynczo są to dobrze zrozumiałe problemy. W zbiorze stanowią one dużą przestrzeń do optymalizacji, którą my (programiści) często pomijamy lub zaniedbujemy. Ludzie nie radzą sobie dobrze z powtarzającym się badaniem tej samej przestrzeni wyszukiwania, zwłaszcza gdy jest ona rozległa. Z kolei komputery świetnie sprawdzają się w tego typu zadaniach.
Odpowiedź na pytanie o dobrą i trwałą strategię optymalizacji obrazów oraz innych zasobów o podobnych właściwościach jest prosta: automatyzacja. Jeśli dostosowujesz swoje zasoby ręcznie, robisz to źle: zapomnisz o tym, znudzisz się lub ktoś inny popełni ten błąd za Ciebie – to pewne.
Saga o programiście dbającym o wydajność
Przeszukiwanie w ramach optymalizacji obrazu obejmuje 2 odrębne fazy: kompilacji i wykonania.
- Niektóre optymalizacje są nieodłącznie związane z samym zasobem, np. wybór odpowiedniego formatu i typu kodowania, dostosowanie ustawień kompresji dla każdego kodera, usunięcie zbędnych metadanych itp. Te czynności można wykonać w czasie kompilacji.
- Inne optymalizacje są określane przez typ i właściwości klienta, który je zażądał, i muszą być wykonywane „w czasie rzeczywistym”. Dotyczy to np. wyboru odpowiedniego zasobu dla DPR klienta i planowanej szerokości wyświetlania, uwzględnienia szybkości sieci klienta, preferencji użytkownika i aplikacji itp.
Narzędzie do tworzenia aplikacji już istnieje, ale można je ulepszyć. Na przykład można zaoszczędzić sporo pieniędzy, dynamicznie dostosowując ustawienie „jakości” dla każdego obrazu i każdego formatu obrazu, ale jeszcze nie widziałem, aby ktoś używał tej funkcji poza badaniami. To obszar, w którym można wprowadzić wiele innowacji, ale w ramach tego wpisu na tym poprzestanę. Skupmy się na części kodu, która jest wykonywana w czasie działania.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
Zamysł aplikacji jest bardzo prosty: pobrać i wyświetlić obraz na 50% widocznego obszaru użytkownika. To miejsce, w którym większość projektantów myje ręce i podąża do baru. Tymczasem deweloper dbający o wydajność ma przed sobą długą noc:
- Aby uzyskać najlepszą kompresję, chce użyć optymalnego formatu obrazu dla każdego klienta: WebP dla Chrome, JPEG XR dla Edge i JPEG dla pozostałych.
- Aby uzyskać najlepszą jakość obrazu, musi wygenerować kilka wariantów każdego obrazu w różnych rozdzielczościach: 1x, 1,5x, 2x, 2,5x, 3x, a może nawet kilka innych.
- Aby uniknąć wyświetlania zbędnych pikseli, musi zrozumieć, co tak naprawdę oznacza „50% widocznego obszaru użytkownika” – istnieje wiele różnych rozmiarów widocznego obszaru.
- W idealnej sytuacji chce też zapewnić odporność na awarie, dzięki której użytkownicy w wolniejszych sieciach będą automatycznie pobierać treści w niższej rozdzielczości. W końcu chodzi o czas na szkło.
- Aplikacja udostępnia też niektóre ustawienia użytkownika, które wpływają na to, które zasoby obrazów należy pobrać.
Projektantka zdaje sobie sprawę, że aby zoptymalizować czytelność, musi wyświetlać inny obraz w 100% szerokości, jeśli rozmiar widocznego obszaru jest mały. Oznacza to, że musimy powtórzyć ten sam proces w przypadku jeszcze jednego zasobu, a potem ustawić pobieranie pod warunkiem rozmiaru widoku. Czy wspominałem, że to trudne? Dobrze, zaczynajmy. Element picture
zapewni nam wystarczającą ilość miejsca:
<picture>
<!-- serve WebP to Chrome and Opera -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
/image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
/image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
type="image/webp">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
/image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
/image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
type="image/webp">
<!-- serve JPEGXR to Edge -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
/image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
/image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
/image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
/image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<!-- serve JPEG to others -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
/image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
/image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
/image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
/image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
<!-- fallback for browsers that don't support picture -->
<img src="/image/thing.jpg" width="50%">
</picture>
Zajęliśmy się kierunkiem artystycznym, wyborem formatu i dostarczeniem 6 wariantów każdego obrazu, aby uwzględnić zmienność DPR i szerokość widoku na urządzeniu klienta. Doskonale.
Element picture
nie pozwala nam jednak zdefiniować żadnych zasad działania na podstawie typu połączenia lub szybkości klienta. Jednak w niektórych przypadkach algorytm przetwarzania pozwala klientowi użytkownika na dostosowanie zasobów, które pobiera (patrz krok 5). Musimy mieć nadzieję, że agent użytkownika jest wystarczająco inteligentny. (Uwaga: żadna z obecnych implementacji nie jest zgodna z tymi standardami). Podobnie w elemencie picture
nie ma żadnych elementów wywołujących, które umożliwiałyby stosowanie logiki specyficznej dla aplikacji uwzględniającej preferencje aplikacji lub użytkownika. Aby uzyskać te 2 ostatnie funkcje, musielibyśmy przenieść całą logikę do kodu JavaScriptu, ale wiązałoby się to z rezygnacją z optymalizacji skanera wstępnego wczytywania, które oferuje picture
. Hm.
Poza tymi ograniczeniami wszystko działa. Przynajmniej w przypadku tego konkretnego zasobu. Prawdziwym, długofalowym wyzwaniem jest to, że nie możemy oczekiwać od projektanta ani programisty, że będą ręcznie pisać kod dla każdego zasobu. Na początku jest to ciekawa łamigłówka, ale szybko traci na atrakcyjności. Potrzebujemy automatyzacji. Być może IDE lub inne narzędzia do przetwarzania treści mogą automatycznie wygenerować powyższy szablon.
Automatyzacja wyboru zasobów za pomocą wskazówek klienta
Weź głęboki oddech, odłóż na bok niedowierzanie i zastanów się nad tym przykładem:
<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
<source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
<img sizes="100vw" src="/image/thing-crop">
</picture>
Powyższy przykład wystarczy do zapewnienia tych samych możliwości co znacznie dłuższy znacznik obrazów powyżej. Poza tym, jak zobaczysz, daje on deweloperowi pełną kontrolę nad tym, jak, które i kiedy zasoby obrazów są pobierane. „Magia” polega na tym, że pierwsza linia umożliwia raportowanie wskazówek klienta i informuje przeglądarkę, aby przekazała serwerowi współczynnik proporcji pikseli urządzenia (DPR
), szerokość układu (Viewport-Width
) i przewidywaną szerokość wyświetlacza (Width
) zasobów.
Jeśli włączysz podpowiedzi po stronie klienta, wynikowy znacznik po stronie klienta będzie zawierał tylko wymagania dotyczące prezentacji. Projektant nie musi się martwić o typy obrazów, rozdzielczości klienta, optymalne punkty przecięcia, aby zmniejszyć liczbę przesyłanych bajtów, ani inne kryteria wyboru zasobów. Powiedzmy sobie szczerze: nigdy nie było to możliwe i nie powinno być możliwe. Co więcej, deweloper nie musi też ponownie pisać i rozwijać powyższego znacznika, ponieważ rzeczywisty wybór zasobów jest negocjowany przez klienta i serwer.
Chrome 46 obsługuje natywne DPR
, Width
i Viewport-Width
. Wskazówki są domyślnie wyłączone, a <meta http-equiv="Accept-CH" content="...">
powyżej służy jako sygnał wyrażenia zgody, który informuje Chrome, aby dołączał określone nagłówki do wychodzących żądań. Teraz przyjrzyjmy się nagłówkom żądania i odpowiedzi w przypadku przykładowego żądania obrazów:
Chrome reklamuje obsługę formatu WebP za pomocą nagłówka żądania Accept; nowa przeglądarka Edge również reklamuje obsługę formatu JPEG XR za pomocą nagłówka Accept.
Kolejne 3 nagłówki żądania to nagłówki wskazówek klienta, które podają współczynnik pikseli urządzenia klienta (3 x), szerokość widocznego obszaru układu (460 pikseli) i przewidywaną szerokość wyświetlania zasobu (230 pikseli). Dzięki temu serwer ma wszystkie niezbędne informacje do wybrania optymalnego wariantu obrazu na podstawie własnych zasad: dostępności zgenerowanych wcześniej zasobów, kosztu ponownego kodowania lub zmiany rozmiaru zasobu, popularności zasobu, bieżącego obciążenia serwera itp. W tym konkretnym przypadku serwer używa wskazówek DPR
i Width
oraz zwraca zasób WebP, co wskazują nagłówki Content-Type
, Content-DPR
i Vary
.
Nie ma tu żadnej magii. Wybór zasobu został przeniesiony z oznaczenia HTML do negocjacji żądania i odpowiedzi między klientem a serwerem. W rezultacie kod HTML dotyczy tylko wymagań dotyczących prezentacji i może być napisany przez każdego projektanta lub programistę, a wyszukiwanie w ramach przestrzeni optymalizacji obrazu jest odkładane na komputery i jest teraz łatwo automatyzowane na dużą skalę. Pamiętasz naszego dewelopera, który zwracał uwagę na wydajność? Jej zadaniem jest teraz napisanie usługi obrazowania, która będzie mogła wykorzystać podane wskazówki i zwrócić odpowiednią odpowiedź. Może użyć dowolnego języka lub serwera, lub pozwolić, aby usługa zewnętrzna lub sieć CDN zrobiła to w jej imieniu.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
Pamiętasz tego gościa? Dzięki podpowiedziom klienta zwykły tag obrazu uwzględnia teraz DPR, widok w oknie przeglądarki i szerokość bez żadnego dodatkowego znacznika. Jeśli chcesz dodać tag art-direction, możesz użyć tagu picture
, jak pokazano powyżej. W przeciwnym razie wszystkie istniejące tagi obrazu stały się znacznie inteligentniejsze. Wskazówki dla klienta wzbogacają istniejące elementy img
i picture
.
Przejmowanie kontroli nad wyborem zasobów za pomocą usługi workera
Skrypt service worker jest w istocie serwerem proxy po stronie klienta działającym w Twojej przeglądarce. Przechwytuje wszystkie wychodzące żądania i pozwala na sprawdzanie, przekształcanie, przechowywanie w pamięci podręcznej, a nawet syntetyzowanie odpowiedzi. Zdjęcia nie są wyjątkiem. Gdy są włączone wskazówki dla klienta, aktywny ServiceWorker może identyfikować żądania obrazów, sprawdzać otrzymane wskazówki dla klienta i definiować własną logikę przetwarzania.
self.onfetch = function(event) {
var req = event.request.clone();
console.log("SW received request for: " + req.url)
for (var entry of req.headers.entries()) {
console.log("\t" + entry[0] +": " + entry[1])
}
...
}
ServiceWorker zapewnia pełną kontrolę po stronie klienta nad wyborem zasobów. To jest bardzo ważne. Pomyśl o tym, jakie możliwości się przed Tobą otwierają:
- Możesz zastąpić wartości nagłówka wskazówek klienta ustawione przez użytkownika.
- Możesz dołączyć do żądania nowe wartości nagłówków wskazówek klienta.
- Możesz zmienić adres URL i wysłać żądanie obrazka do alternatywnego serwera (np. CDN).
- Możesz nawet przenieść wartości wskazówek z nagłówków do samego adresu URL, jeśli ułatwi to wdrożenie w Twojej infrastrukturze.
- Możesz przechowywać w pamięci podręcznej odpowiedzi i definiować własną logikę dotyczącą tego, które zasoby mają być wyświetlane.
- Możesz dostosować odpowiedź na podstawie połączenia użytkownika.
- Możesz użyć interfejsu NetInfo API, aby wysłać zapytanie do serwera i przekazać swoje preferencje.
- Jeśli pobieranie trwa zbyt długo, możesz zwrócić alternatywną odpowiedź.
- Możesz uwzględnić zastąpienia ustawień aplikacji i użytkownika.
- Możesz… robić wszystko, co tylko chcesz.
Element picture
zapewnia w znacznikach HTML niezbędną kontrolę nad kierunkiem artystycznym.
Wskazówki klienta zawierają adnotacje dotyczące żądań obrazów, które umożliwiają automatyzację wyboru zasobów. ServiceWorker zapewnia obsługę żądań i odpowiedzi po stronie klienta. To jest rozszerzalna sieć w akcji.
Najczęstsze pytania dotyczące wskazówek dotyczących klienta
Gdzie są dostępne wskazówki dotyczące klienta? Dostępna w Chrome 46. Rozważane w Firefox i Edge.
Dlaczego akceptacja podpowiedzi klienta jest wymagana? Chcemy zminimalizować koszty związane z witrynami, które nie będą używać wskazówek klienta. Aby włączyć podpowiedzi klienta, witryna powinna zawierać nagłówek
Accept-CH
lub równoważną dyrektywę<meta http-equiv>
w znacznikach strony. Jeśli jest obecny jeden z tych atrybutów, klient użytkownika doda odpowiednie wskazówki do wszystkich żądań podzasobów. W przyszłości możemy udostępnić dodatkowy mechanizm, który pozwoli zachować te ustawienia w przypadku określonego źródła. Umożliwi to wyświetlanie tych samych wskazówek w przypadku żądań nawigacji.Dlaczego potrzebujemy wskazówek dotyczących klienta, skoro mamy ServiceWorker? ServiceWorker nie ma dostępu do informacji o układzie, zasobach i szerokości widocznego obszaru. Nie powoduje to jednak wprowadzenia kosztownych operacji w obie strony ani znacznego opóźnienia żądania obrazu, np. gdy żądanie obrazu jest inicjowane przez parsujący preloader. Wskazówki klienta są zintegrowane z przeglądarką, aby udostępniać te dane w ramach żądania.
Czy wskazówki dla klienta dotyczą tylko zasobów graficznych? Głównym przypadkiem użycia wskazówek DPR, Viewport-Width i Width jest umożliwienie wyboru zasobów dla komponentów z obrazem. Jednak te same wskazówki są dostarczane do wszystkich zasobów podrzędnych niezależnie od ich typu. Na przykład żądania kodu CSS i JavaScript również otrzymują te same informacje i mogą być używane do optymalizacji tych zasobów.
Dlaczego niektóre żądania obrazów nie podają wartości Szerokość? Przeglądarka może nie znać docelowej szerokości wyświetlania, ponieważ witryna opiera się na rozmiarze własnym obrazu. W rezultacie w przypadku takich żądań oraz żądań, które nie mają „szerokości wyświetlania” (np. zasobu JavaScript), pomijamy wskazówkę Szerokość. Aby otrzymywać wskazówki dotyczące szerokości, pamiętaj o podaniu wartości rozmiarów w przypadku swoich obrazów.
A co z <insert my favorite hint>? ServiceWorker umożliwia deweloperom przechwytywanie i modyfikowanie (np. dodawanie nowych nagłówków) wszystkich wychodzących żądań. Na przykład można łatwo dodać informacje oparte na NetInfo, aby wskazać bieżący typ połączenia. Więcej informacji znajdziesz w artykule „Raportowanie możliwości za pomocą ServiceWorkera”. „Natywne” wskazówki dostarczane w Chrome (DPR, Width, Resource-Width) są implementowane w przeglądarce, ponieważ czysta implementacja oparta na oprogramowaniu spowodowałaby opóźnienie wszystkich żądań obrazów.
Gdzie mogę dowiedzieć się więcej, zobaczyć więcej wersji demonstracyjnych i co jeszcze? Zapoznaj się z dokumentem z informacjami. Jeśli masz opinię lub inne pytania, otwórz zgłoszenie na GitHubie.