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

Ilia Grigorik

Reklamy w internecie zapewniają niezrównany zasięg. Wystarczy kliknąć, aby uzyskać dostęp do aplikacji na niemal każdym połączonym urządzeniu: smartfonie, tablecie, laptopie, komputerze i telewizorze, niezależnie od marki czy platformy. Aby zapewnić użytkownikom najlepsze wrażenia, udało Ci się stworzyć witrynę elastyczną, która dostosowuje sposób prezentacji i funkcje do poszczególnych formatów. Teraz przechodzisz na listę kontrolną dotyczącą wydajności, aby aplikacja wczytywała się tak szybko, jak to możliwe: zoptymalizowano krytyczną ścieżkę renderowania, skompresowano i zapisano w pamięci podręcznej zasoby tekstowe, a teraz często analizujesz przeniesione bajty zasobów z obrazów. Problem polega na tym, że optymalizacja obrazu jest trudna:

  • Określ odpowiedni format (format wektorowy lub rastrowy)
  • Ustal optymalne formaty kodowania (JPEG, WebP itp.).
  • Określić odpowiednie ustawienia kompresji (strumień lub bezstratny).
  • Określ, które metadane należy zachować, a które usunąć
  • Utwórz kilka wariantów każdego wyświetlacza i rozdzielczości DPR
  • ...
  • Uwzględnij typ sieci, szybkość i preferencje użytkownika

Indywidualnie jest to dobrze rozumiane problemy. Łącznie tworzą one ogromną przestrzeń optymalizacji, którą my (programiści) często pomijamy lub zaniedbujemy. Ludzie nie radzą sobie wielokrotnie z wielokrotnym eksplorowaniem tej samej przestrzeni wyszukiwania, zwłaszcza gdy wymaga ona wielu działań. Z drugiej strony komputery radzą sobie z tego typu zadaniami.

Odpowiedź na dobrą i zrównoważoną strategię optymalizacji obrazów i innych zasobów o podobnych właściwościach jest prosta: automatyzacja. Jeśli ręcznie dopracowujesz zasoby, robisz to źle: zapomnisz, zaczniesz leniwie, albo ktoś zrobi to za Ciebie.

Saga deweloperów, którzy zwracają uwagę na wydajność

Optymalizacja wyszukiwania obrazów składa się z 2 faz: czasu kompilacji i czasu wykonywania.

  • Niektóre optymalizacje są nieodłączną częścią zasobu, np. wybór odpowiedniego formatu i typu kodowania, dostrajanie ustawień kompresji dla każdego kodera czy usunięcie zbędnych metadanych. Te czynności można wykonać w „czasie kompilacji”.
  • Inne optymalizacje są określane na podstawie typu i właściwości klienta, który je żąda, i muszą być wykonywane w czasie działania: wybierając odpowiedni zasób odpowiadający DPR klienta i zamierzonej szerokości wyświetlania, uwzględniając szybkość sieci klienta, preferencje użytkownika i aplikacji itd.

Narzędzie do tworzenia kompilacji jest dostępne, ale można by je ulepszyć. Na przykład dzięki dynamicznemu dostosowywaniu ustawienia „jakości” dla każdego obrazu i każdego formatu można by liczyć sporo oszczędności. Jeszcze nie wiem, czy ktoś używa ich poza badaniami. Jest to obszar pełen innowacji, ale na razie zostawię ten post na tym forum. Skupmy się na momentach organizowanych w czasie rzeczywistym.

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

Intencja aplikacji jest bardzo prosta: pobierz i wyświetl obraz na 50% obszaru widocznego dla użytkownika. To tutaj większość projektantów umywa ręce i głowę przed barem. Tymczasem programista w zespole, któremu zależy na wydajności, ma długą noc:

  1. Aby uzyskać najlepszą kompresję, dla każdego klienta klient chce użyć optymalnego formatu: WebP dla Chrome, JPEG XR dla Edge i JPEG dla pozostałych klientów.
  2. Aby uzyskać najlepszą jakość wizualną, musi wygenerować wiele wariantów każdego obrazu w różnych rozdzielczościach: 1x, 1,5x, 2x, 2,5x, 3x, a nawet kilka innych.
  3. Aby uniknąć wyświetlania niepotrzebnych pikseli, Gabriela musi wiedzieć, co oznacza „50% obszaru widocznego dla użytkownika”, ponieważ występuje dużo różnych szerokości widocznego obszaru.
  4. W miarę możliwości chce też zapewnić odporność, aby użytkownicy korzystający z wolniejszych sieci automatycznie pobierali mniejszą rozdzielczość. W końcu nadchodzi czas na naukę.
  5. Aplikacja udostępnia też pewne elementy sterujące dla użytkowników, które wpływają na to, który zasób obrazu należy pobrać, więc należy to uwzględnić.

Następnie okazuje się, że musi wyświetlić inny obraz o szerokości 100%, jeśli widoczny obszar jest mały, żeby zoptymalizować czytelność. Musimy więc powtórzyć ten proces dla kolejnego zasobu, a potem ustawić pobieranie warunkowe w zależności od rozmiaru widocznego obszaru. Wspominałam, że to jest trudne? No dobra, do dzieła. Element picture zaprowadzi 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>

Przeanalizowaliśmy kierunek grafiki, wybór formatu i 6 wariantów każdego obrazu, aby uwzględnić zmienność DPR i szerokości widocznego obszaru na urządzeniu klienta. Doskonale.

Niestety element picture nie pozwala nam określić żadnych reguł dotyczących działania w zależności od typu połączenia lub szybkości połączenia klienta. Algorytm przetwarzania umożliwia klientowi użytkownika dostosowanie w niektórych przypadkach pobieranych zasobów – patrz krok 5. Miejmy tylko nadzieję, że klient użytkownika jest wystarczająco inteligentny. (uwaga: żadna z obecnych implementacji nie jest dostępna). Podobnie w elemencie picture nie ma punktów zaczepienia umożliwiających logikę specyficzną dla aplikacji, która odpowiada preferencjom aplikacji lub użytkownika. Aby uzyskać te 2 ostatnie elementy, musielibyśmy przenieść wszystkie powyższe funkcje do kodu JavaScriptu, co rezygnuje z optymalizacji skanera w ramach funkcji picture. Hm.

Poza tymi ograniczeniami działa to. Przynajmniej w przypadku tego zasobu. Rzeczywiste i długotrwałe wyzwanie polega na tym, że nie oczekujemy, że projektant lub programista będzie ręcznie opracowywał taki kod dla każdego zasobu. Za pierwszym razem jest to ciekawa łamigłówka, ale zaraz potem znika. Potrzebujemy automatyzacji. Być może IDE lub inne narzędzie do przekształcania treści pomoże nam uratować naszą pracę i automatycznie wygenerować powtarzalny schemat.

Automatyzowanie wyboru zasobów za pomocą wskazówek dla klienta

Weź głęboki oddech, zatrzymaj niedowierzanie, a teraz przyjrzyj się temu przykładowi:

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

Wydaje mi się, że powyższy przykład wystarczy do udostępnienia tych samych funkcji, co znacznie dłuższy znacznik obrazów powyżej, oraz, jak zobaczymy, w pełni steruje to pobieraniem zasobów graficznych, w jaki sposób i kiedy. Parametr „magic” (magiczny) umożliwia raportowanie wskazówek dla klienta i informuje przeglądarkę, by przesyłała do serwera informacje o współczynniku pikseli urządzenia (DPR), szerokości widocznego obszaru (Viewport-Width) i zamierzonej szerokości wyświetlacza (Width) zasobów.

Po włączeniu wskazówek dla klienta wynikowe znaczniki po stronie klienta zachowują tylko wymagania dotyczące prezentacji. Projektant nie musi martwić się typami obrazów, rozdzielczościami po stronie klienta, optymalnymi punktami przerwania, które zmniejszają ilość dostarczanych bajtów, ani innymi kryteriami wyboru zasobów. Naprawdę nie muszą tego robić. Nie muszą tego robić. Co więcej, deweloper nie musi przepisywać i rozwijać powyższych znaczników, ponieważ 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 powyższy <meta http-equiv="Accept-CH" content="..."> służy jako sygnał akceptacji, który informuje Chrome, że ma dołączyć określone nagłówki do żądań wychodzących. Gdy to zrobisz, sprawdźmy nagłówki żądania i odpowiedzi dla przykładowego żądania obrazu:

Diagram negocjacji wskazówek dotyczących klienta

Chrome informuje o obsłudze formatu WebP przez nagłówek Accept request. Nowa przeglądarka Edge podobnie reklamuje obsługę formatu JPEG XR w nagłówku Accept.

Kolejne 3 nagłówki żądania to nagłówki wskazówek klienta reklamujące współczynnik pikseli urządzenia klienta (3x), szerokość widocznego obszaru układu (460 pikseli) oraz zamierzoną szerokość wyświetlania zasobu (230 pikseli). Przekazuje to serwerowi wszystkie informacje niezbędne do wyboru optymalnego wariantu obrazu na podstawie własnego zestawu 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 przypadku serwer korzysta ze wskazówek DPR i Width oraz zwraca zasób WebP wskazywany przez nagłówki Content-Type, Content-DPR i Vary.

Nie ma tu magii. Przenieśliśmy proces wyboru zasobów z znaczników HTML do procesu negocjowania żądania i odpowiedzi między klientem a serwerem. Dlatego kod HTML odpowiada tylko wymaganiom dotyczącym prezentacji i jest czymś, co może napisać każdy projektant i programista. Z kolei wyszukiwanie w obszarze optymalizacji obrazów jest odłożone na komputery i można go łatwo zautomatyzować na dużą skalę. Pamiętasz dewelopera, który dba o wydajność? Jej zadaniem jest teraz napisanie usługi graficznej, która wykorzysta podane wskazówki i zwraca odpowiednią odpowiedź: może użyć dowolnego języka i dowolnego serwera albo zlecić to usłudze zewnętrznej 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 skromny tag graficzny jest teraz rozpoznawany pod kątem DPR, widocznego obszaru i szerokości bez dodatkowych znaczników. Jeśli chcesz dodać wskazówki graficzne, możesz użyć tagu picture, jak pokazano powyżej. W przeciwnym razie wszystkie Twoje dotychczasowe tagi graficzne stały się znacznie bardziej inteligentne. Wskazówki dla klientów ulepszają istniejące elementy img i picture.

Kontrola nad wyborem zasobów za pomocą skryptu service worker

ServiceWorker to w rzeczywistości serwer proxy po stronie klienta działający w przeglądarce. Przechwytuje wszystkie żądania wychodzące i umożliwia sprawdzanie, przepisywanie, buforowanie, a nawet syntetyzowanie odpowiedzi. Obrazy nie różnią się od siebie, a po włączeniu wskazówek dotyczących klienta aktywny ServiceWorker może identyfikować żądania dotyczące obrazów, sprawdzać dostarczone wskazówki dotyczące 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])
    }
    ...
}
Skrypt service worker ze wskazówkami dotyczącymi klienta.

Usługa ServiceWorker zapewnia pełną kontrolę po stronie klienta nad wyborem zasobów. Ma to kluczowe znaczenie. Trzeba to uchwycić, ponieważ możliwości są nieskończone:

  • Możesz przepisać wartości nagłówków wskazówek dotyczących klienta ustawione przez klienta użytkownika.
  • 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 podpowiedzi z nagłówków i do samego adresu URL, jeśli ułatwi to wdrożenie w Twojej infrastrukturze.
  • Możesz buforować odpowiedzi i zdefiniować własną logikę dla zasobów, które będą udostępniane.
  • Możesz dostosowywać odpowiedź w zależności od połączenia między użytkownikami.
  • Możesz uwzględnić zastąpienia preferencji aplikacji i użytkownika.
  • Możesz... robić wszystko, co tylko chcesz.

Element picture umożliwia kontrolowanie kierunku grafiki w znacznikach HTML. Wskazówki dotyczące klienta zawierają adnotacje do wynikowych żądań obrazu, które umożliwiają automatyzację wyboru zasobów. ServiceWorker zapewnia możliwości zarządzania żądaniami i odpowiedziami po stronie klienta. To właśnie jest ekspansywna sieć w praktyce.

Wskazówki dla klienta Najczęściej zadawane pytania

  1. Gdzie są dostępne wskazówki dla klienta? Wysyłana w Chrome 46. Trwa rozpatrywanie w Firefoksie i Edge.

  2. Dlaczego wskazówki dla klientów są włączone? Chcemy zminimalizować nakład pracy w przypadku witryn, które nie korzystają ze wskazówek dla klienta. Aby umożliwić korzystanie ze wskazówek dla klienta, należy umieścić w znacznikach strony nagłówek Accept-CH lub odpowiednik dyrektywy <meta http-equiv>. Jeśli tak się stanie, klient użytkownika będzie dołączać odpowiednie wskazówki do wszystkich żądań zasobów podrzędnych. W przyszłości możemy wprowadzić dodatkowy mechanizm utrzymywania tego ustawienia w przypadku konkretnego źródła, co pozwoli na dostarczanie tych samych wskazówek w przypadku żądań dotyczących nawigacji.

  3. Dlaczego potrzebujemy wskazówek klienta, jeśli mamy skrypt ServiceWorker? Skrypt service worker nie ma dostępu do informacji o układzie, zasobach ani szerokości widocznego obszaru. No i to przynajmniej na kosztownych transferach w obie strony i znacznym opóźnieniu żądania obrazu, np. wtedy, gdy parser wstępnego wczytywania zainicjuje takie żądanie. Wskazówki dotyczące klienta integrują się z przeglądarką, aby udostępniać te dane w ramach żądania.

  4. Czy wskazówki dla klienta dotyczą tylko zasobów graficznych? Głównym przypadkiem użycia wskazówek dotyczących DPR, szerokości i szerokości widocznego obszaru jest włączenie wyboru zasobów dla komponentów z obrazem. Te same wskazówki dotyczą wszystkich zasobów podrzędnych, niezależnie od ich typu – np. żądania CSS i JavaScript również uzyskują te same informacje i można je wykorzystać do optymalizacji zasobów.

  5. Niektóre żądania dotyczące obrazów nie zgłaszają szerokości? Przeglądarka może nie znać zamierzonej szerokości wyświetlanego obrazu, ponieważ witryna zależy od pierwotnego rozmiaru obrazu. W efekcie wskazówka dotycząca szerokości jest pomijana w przypadku takich żądań i żądań, które nie mają określonej szerokości wyświetlania, np. zasobu JavaScript. Aby otrzymywać wskazówki dotyczące szerokości, określ w obrazach wartość rozmiaru.

  6. A co z <wstaw moją ulubioną wskazówkę>? 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 wskażą bieżący typ połączenia – patrz sekcja „Raportowanie możliwości za pomocą obiektu ServiceWorker”. „Natywne” wskazówki wyświetlane w Chrome (DPR, Szerokość, Szerokość zasobu) są wdrażane w przeglądarce, ponieważ implementacja oparta na samym kodzie SW opóźnia żądania dotyczące wszystkich obrazów.

  7. Gdzie znajdę więcej informacji i przykłady? Zapoznaj się z dokumentem wyjaśniającym, a jeśli masz jakieś uwagi lub masz inne pytania, otwórz problem na GitHubie.