Automatyzacja wyboru zasobów przy użyciu wskazówek dla klienta

Tworzenie reklam w internecie daje Ci niezrównany zasięg. Wystarczy kliknąć, aby uruchomić aplikację na prawie każdym połączonym urządzeniu – smartfonie, tablecie, laptopie, komputerze stacjonarnym, telewizorze, a także innej marce czy platformie. Aby zapewnić jak największą wygodę, stworzyliśmy witrynę elastyczną, która dostosowuje sposób prezentacji i funkcjonalność każdego formatu. Teraz skreślasz listę kontrolną wydajności, aby zapewnić jak najszybsze ładowanie aplikacji: zoptymalizowano krytyczną ścieżkę renderowania, skompresowano i zapisano w pamięci podręcznej zasoby tekstowe, a obecnie analizujemy większość zasobów graficznych, które często są przenoszone na konto. Problem w tym, że optymalizacja obrazów jest trudna:

  • Ustal odpowiedni format (wektor lub rastrowy).
  • Określ optymalne formaty kodowania (JPEG, WebP itp.)
  • Określ odpowiednie ustawienia kompresji (stratna lub bezstratna)
  • Określ, które metadane należy zachować, a które usunąć
  • Utwórz kilka wariantów każdego modelu dla każdego wyświetlacza + rozdzielczość DPR
  • ...
  • Uwzględniaj typ, szybkość i preferencje użytkownika w sieci

Poszczególne problemy są dobrze rozumiane. W sumie tworzą one dużą przestrzeń optymalizacji, którą my (deweloperzy) często pomijamy lub zaniedbujemy. Ludzie nie mają zbytniej pracy, bo ciągle badają oni tę samą przestrzeń wyszukiwania, zwłaszcza gdy składają się na nią wiele etapów. Z kolei komputery świetnie radzą sobie z tego typu zadaniami.

Odpowiedź na dobrą i zrównoważoną strategię optymalizacji obrazów oraz innych zasobów o podobnych właściwościach jest prosta – automatyzacja. Jeśli ręcznie udoskonalasz swoje zasoby, robisz to źle – zapomnisz, będziesz się lenić, albo ktoś inny popełni ten błąd za Ciebie.

Historia dewelopera kładącego nacisk na wydajność

Przestrzeń optymalizacji obrazów według własnego uznania dzieli się na 2 fazy: w czasie kompilacji i w czasie wykonywania.

  • Niektóre optymalizacje są nieodłączne dla samych zasobów.Chodzi np. o wybór odpowiedniego formatu i typu kodowania, dostrajanie ustawień kompresji dla każdego kodera, usunięcie zbędnych metadanych itp. Te kroki można wykonać w czasie kompilacji.
  • Inne optymalizacje zależą od typu i właściwości klienta, który je zażąda, i należy je przeprowadzić w czasie działania: wybierz odpowiednie zasoby dla DPR klienta i zamierzonej szerokości wyświetlania, uwzględniając szybkość sieci, preferencje użytkownika i aplikacji itd.

Narzędzia do zarządzania czasem kompilacji istnieją, ale można je ulepszyć. Na przykład dzięki dynamicznemu dostrajaniu ustawienia jakości każdego obrazu i każdego formatu można uzyskać dużo oszczędności, ale nie sądzę, aby ktokolwiek poza badaniami korzystał z tej funkcji. To obszar, który jest dość innowacyjny, ale zostawię go na potrzeby tego posta. Skupmy się na przebiegu historii.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Intencja aplikacji jest bardzo prosta: pobierają i wyświetlają obraz w 50% widocznego obszaru użytkownika. To w tym miejscu każdy projektant myje rękę i głowę na barze. Tymczasem deweloper z zespołu, któremu zależy na wydajności, ma długą noc:

  1. Aby uzyskać najlepszą kompresję, Anna chce używać optymalnego formatu obrazu dla każdego klienta: WebP w Chrome, JPEG XR w przypadku Edge i JPEG dla pozostałych.
  2. Aby uzyskać najlepszą jakość wizualną, Anna 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 opcji pomiędzy.
  3. Aby uniknąć wyświetlania zbędnych pikseli, Jola musi wiedzieć, co tak naprawdę oznacza „50% widocznego obszaru użytkownika” – jest tam wiele różnych szerokości widocznego obszaru.
  4. W idealnej sytuacji warto zadbać też o elastyczność, która polega na tym, że użytkownicy w wolniejszych sieciach będą automatycznie pobierać treści w niższej rozdzielczości. W końcu czas na szkło.
  5. Aplikacja udostępnia też użytkownikom kontrolę nad tym, który zasób obrazu powinien zostać pobrany, więc trzeba też to uwzględnić.

A następnie projektant uświadamia sobie, że musi wyświetlać inny obraz w 100% szerokości, jeśli widoczny obszar jest mały, aby zapewnić czytelność. Musimy teraz powtórzyć ten sam proces dla jeszcze jednego zasobu, a potem uzależnić pobieranie od rozmiaru widocznego obszaru. Czy wspomniałem(-am), że to trudne? No dobra, zaczynajmy. Element picture zajmie nas dość daleko:

<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>

Omówiliśmy kierunek artystyczny i wybór formatu oraz udostępniliśmy 6 wariantów każdego obrazu, aby uwzględnić zmienność widoku danych i szerokości widocznego obszaru na urządzeniu klienta. Doskonale.

Niestety element picture nie pozwala nam zdefiniować żadnych reguł jego działania zależnych od typu lub szybkości połączenia klienta. Jednak w niektórych przypadkach algorytm przetwarzania umożliwia klientowi użytkownika dostosowanie pobieranych zasobów (patrz krok 5). Wystarczy mieć nadzieję, że klient użytkownika jest wystarczająco sprytny. Uwaga: żadna z obecnych implementacji nie jest. Analogicznie w elemencie picture nie ma elementów zaczepiających, które umożliwiają zastosowanie logiki uwzględniającej preferencje aplikacji lub użytkownika. Aby uzyskać te 2 ostatnie bity, musielibyśmy przenieść całą powyższą logikę do JavaScriptu, ale to nie działa z optymalizacją skanera wstępnego wczytywania oferowaną przez picture. Hm.

Poza tymi ograniczeniami działa. Przynajmniej w przypadku tego konkretnego zasobu. Prawdziwe, a jednocześnie długoterminowe wyzwanie polega na tym, że nie spodziewamy się, że projektant lub programista będą ręcznie tworzyć kod w ten sposób dla każdego zasobu. Przy pierwszej próbie to ciężka łamigłówka, ale potem przestaje być atrakcyjna. Potrzebujemy automatyzacji. Być może IDE lub inne narzędzie do przekształcania treści może nas uratować i automatycznie wygenerować powtarzalny schemat.

Zautomatyzowanie wyboru zasobów na podstawie wskazówek dotyczących klienta

Weź głęboki oddech, złóż niedowierzanie i zastanów się nad następującym 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>

Może ciężko wierzyć, ale ten przykład daje takie same możliwości jak znaczniki obrazu powyżej, a dodatkowo, jak widać, deweloper ma pełną kontrolę nad tym, jak, które i kiedy są pobierane zasoby graficzne. Magia znajduje się w pierwszym wierszu, który umożliwia raportowanie wskazówek dla klientów i informuje przeglądarkę, że ma informować serwer o współczynniku pikseli urządzenia (DPR), szerokości widocznego obszaru układu (Viewport-Width) oraz planowanej szerokości wyświetlania (Width) zasobów.

Po włączeniu wskazówek dla klienta powstałe znaczniki po stronie klienta zachowują tylko wymagania dotyczące prezentowania. Projektant nie musi martwić się o typy obrazów, rozdzielczość klienta, optymalne punkty przerwania w celu zmniejszenia liczby dostarczonych bajtów ani inne kryteria wyboru zasobów. Spójrzmy prawdzie w oczy, nigdy tego nie zrobili i nie powinni. Co więcej, programista nie musi przepisywać ani rozszerzać powyższych znaczników, ponieważ rzeczywisty wybór zasobów jest negocjowany przez klienta i serwer.

Chrome 46 zapewnia natywną obsługę wskazówek DPR, Width i Viewport-Width. Wskazówki są domyślnie wyłączone, a zasada <meta http-equiv="Accept-CH" content="..."> powyżej służy jako sygnał zgody, który informuje Chrome, że ma dołączać określone nagłówki do żądań wychodzących. Mając to na uwadze, sprawdzimy nagłówki żądania i odpowiedzi w przykładowym żądaniu obrazu:

Diagram negocjacji wskazówek klienta

Chrome reklamuje obsługę formatu WebP za pomocą nagłówka Accept request, a nowa przeglądarka Edge podobnie reklamuje obsługę JPEG XR w nagłówku Accept.

Następne 3 nagłówki żądania to nagłówki ze wskazówkami dla klienta promujące współczynnik pikseli urządzenia klienta (3x), szerokość widocznego obszaru układu (460 pikseli) oraz docelową szerokość zasobu (230 pikseli). Zapewnia to serwerowi wszystkie informacje niezbędne do wyboru optymalnego wariantu obrazu na podstawie własnego zbioru zasad: dostępność wstępnie wygenerowanych zasobów, koszt ponownego kodowania lub zmiany rozmiaru zasobu, popularność zasobu, bieżące obciążenie serwera itd. W tym konkretnym przypadku serwer używa wskazówek DPR i Width, a następnie zwraca zasób WebP wskazywany przez nagłówki Content-Type, Content-DPR i Vary.

Nie ma tu magii. Przenieśliśmy wybór zasobów ze znaczników HTML i do negocjacji między klientem a serwerem. Dlatego kod HTML uwzględnia wyłącznie wymagania dotyczące prezentacji, można więc zaufać projektowi i programistom, podczas gdy wyszukiwanie w obszarze optymalizacji obrazów jest odroczone do komputerów i obecnie można łatwo zautomatyzować na dużą skalę. Pamiętasz naszego dewelopera, któremu zależy na wydajności? Jej zadaniem jest napisanie usługi graficznej, która będzie korzystać z podanych wskazówek i zwraca odpowiednią odpowiedź: może używać dowolnego języka lub serwera albo zlecić to usłudze innej firmy lub sieci CDN.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Pamiętasz też tego gościa powyżej? Dzięki wskazówkom klienta w prosty sposób tag graficzny uwzględnia teraz DPR, widoczny obszar i szerokość bez żadnych dodatkowych znaczników. Jeśli chcesz dodać wskazówki dotyczące grafiki, możesz użyć tagu picture, jak pokazano powyżej. Poza tym wszystkie istniejące tagi graficzne stały się o wiele bardziej inteligentne. Wskazówki klienta wzbogacają istniejące elementy img i picture.

Przejęcie kontroli nad wyborem zasobów za pomocą skryptu service worker

Skrypt ServiceWorker jest serwerem proxy po stronie klienta działającym w przeglądarce. Przechwytuje wszystkie żądania wychodzące i umożliwia sprawdzanie, przepisywanie, przechowywanie w pamięci podręcznej, a nawet syntetyzowanie odpowiedzi. Obrazy nie różnią się od siebie, a po włączeniu wskazówek klienta aktywny mechanizm ServiceWorker może identyfikować żądania obrazu, analizować dostarczone wskazówki dotyczące klientów 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 instrukcji dotyczących klienta.

ServiceWorker zapewnia pełną kontrolę po stronie klienta nad wyborem zasobów. Ma to kluczowe znaczenie. Dajmy spokój, bo możliwości są niemal nieskończone.

  • Wartości nagłówka wskazówek dla klienta ustawione przez klienta użytkownika możesz zapisać ponownie.
  • Do żądania możesz dołączyć nowe wartości nagłówków wskazówek dla klienta.
  • Możesz przepisać adres URL i skierować żądanie obrazu na alternatywny serwer (np. CDN).
    • Możesz nawet przenieść wartości wskazówek z nagłówków do samego adresu URL, jeśli ułatwia to wdrażanie rozwiązań w Twojej infrastrukturze.
  • Możesz zapisywać odpowiedzi w pamięci podręcznej i zdefiniować własną logikę udostępniania zasobów.
  • Możesz dostosować swoje odpowiedzi do połączeń użytkowników.
  • Możesz uwzględniać zastąpienia ustawień aplikacji i użytkownika.
  • Możesz... robić, co tylko chcesz, naprawdę.

Element picture zapewnia niezbędną kontrolę kierunku grafiki w znacznikach HTML. Wskazówki klienta udostępniają adnotacje dotyczące powstałych żądań obrazu, które umożliwiają automatyzację wyboru zasobów. ServiceWorker udostępnia funkcje zarządzania żądaniami i odpowiedziami na kliencie. Oto właśnie rozszerzona sieć w praktyce.

Wskazówki dla klienta – najczęstsze pytania

  1. Gdzie są dostępne wskazówki dla klientów? Wysłano w Chrome 46. W trakcie sprawdzania w Firefoksie i Edge.

  2. Dlaczego aplikacja jest akceptowana w przypadku wskazówek dla klientów? Chcemy zminimalizować narzut witryn, które nie korzystają ze wskazówek dla klientów. Aby włączyć wskazówki dla klienta, witryna powinna zawierać nagłówek Accept-CH lub odpowiednią dyrektywę <meta http-equiv> w znacznikach strony. W przypadku użycia którejś z tych metod klient użytkownika będzie dołączać odpowiednie wskazówki do wszystkich żądań zasobów podrzędnych. W przyszłości możemy udostępnić dodatkowy mechanizm utrwalający tę preferencję w przypadku określonego punktu początkowego, który pozwoli na podawanie tych samych wskazówek w żądaniach nawigacji.

  3. Dlaczego potrzebujemy wskazówek od klienta, jeśli mamy ServiceWorker? ServiceWorker nie ma dostępu do informacji o układzie, zasobach ani szerokości widocznego obszaru. Przynajmniej możliwe będzie wprowadzenie kosztownych transferów w obie strony i znacznego opóźnienia żądania obrazu, np. gdy jest ono inicjowane przez parser wstępnego wczytywania. Wskazówki dotyczące klienta integrują się z przeglądarką, aby udostępniać te dane w ramach żądania.

  4. Czy wskazówki klienta dotyczą tylko zasobów graficznych? Podstawowym przypadkiem użycia wskazówek dotyczących DSP, Szerokość widocznego obszaru i Szerokość jest włączenie wyboru zasobów dla zasobów graficznych. Te same wskazówki są jednak dostarczane w przypadku wszystkich zasobów podrzędnych niezależnie od ich typu – np. żądania CSS i JavaScript również uzyskują te same informacje i można ich używać do optymalizacji tych zasobów.

  5. Niektóre żądania obrazu nie podają szerokości. Dlaczego? Przeglądarka może nie znać docelowej szerokości wyświetlania, ponieważ witryna polega na wewnętrznym rozmiarze obrazu. W efekcie wskazówka dotycząca szerokości jest pomijana w przypadku takich żądań oraz żądań, które nie mają parametru „szerokości wyświetlanej” – np. zasobu JavaScript. Aby otrzymywać wskazówki dotyczące szerokości, musisz określić wartość rozmiarów obrazów.

  6. Co z <wstaw moją ulubioną wskazówkę>? Narzędzie ServiceWorker umożliwia programistom przechwytywanie i modyfikowanie (np. dodawanie nowych nagłówków) wszystkich żądań wychodzących. Można na przykład łatwo dodać informacje oparte na NetInfo, które wskazują bieżący typ połączenia – patrz Raportowanie możliwości za pomocą ServiceWorker. „Natywne” wskazówki udostępniane w Chrome (DPR, Szerokość, Szerokość zasobu) są implementowane w przeglądarce, ponieważ implementacja oparta na oprogramowaniu opóźnia wszystkie żądania obrazów.

  7. Gdzie mogę dowiedzieć się więcej, zobaczyć więcej prezentacji i co z nimi? Zapoznaj się z dokumentem objaśniającym. Jeśli chcesz podzielić się opinią lub masz inne pytania, zgłoś problem na GitHubie.