Składnia opisowa

W tym module dowiesz się, jak zapewnić przeglądarce wybór obrazów, aby mogła ona sama wybrać, co jest wyświetlane. srcset nie służy do zastępowania źródeł obrazów w określonych punktach przerwania ani nie służy do zastępowania innych obrazów. Te składniki pozwalają przeglądarce rozwiązać bardzo trudny problem, niezależnie od nas: bezproblemowe żądanie i renderowanie źródła obrazu dopasowanego do kontekstu przeglądania, w tym rozmiaru widocznego obszaru, gęstości interfejsu, preferencji użytkownika, przepustowości i wielu innych czynników.

To bardzo ważna sprawa – z pewnością znacznie więcej niż powinniśmy brać pod uwagę, kiedy po prostu oznaczamy obraz do umieszczenia w sieci. Właściwe wykonywanie tego zadania wymaga więcej informacji, niż mamy do nich dostęp.

Opisywanie gęstości za pomocą funkcji x

Elementy typu <img> o stałej szerokości zajmują taką samą część widocznego obszaru w każdym kontekście przeglądania, niezależnie od gęstości wyświetlacza użytkownika, czyli liczby pikseli fizycznych umieszczonych na ekranie. Na przykład obraz o wbudowanej szerokości 400px będzie zajmować prawie cały widoczny obszar w przeglądarce zarówno na pierwotnym, jak i dużo nowszym telefonie Google Pixel 6 Pro. Oba urządzenia mają znormalizowany obszar o szerokości 412px piksela logicznego.

Pixel 6 Pro ma jednak znacznie ostry wyświetlacz. Telefony 6 Pro mają rozdzielczość fizyczną 1440 × 3120 pikseli, a Pixel 1080 × 1920 pikseli, czyli liczbę pikseli sprzętowych tworzących sam ekran.

Współczynnik między pikselami logicznymi a fizycznymi to współczynnik pikseli urządzenia dla danego wyświetlacza. Wartość DPR oblicza się, dzieląc rzeczywistą rozdzielczość ekranu urządzenia przez liczbę pikseli CSS widocznego obszaru.

DPR w wysokości 2 wyświetlany w oknie konsoli.

Oznacza to, że oryginalny Pixel – 2,6, a Pixel 6 Pro – 3,5.

W przypadku iPhone'a 4, pierwszego urządzenia o wskaźniku DPR większej niż 1, współczynnik pikseli urządzenia wynosi 2. Fizyczna rozdzielczość ekranu jest 2 razy większa. Każde urządzenie sprzed iPhone'a 4 miało DPR równą 1: od jednego piksela logicznego na jeden piksel fizyczny.

Jeśli wyświetlisz ten obraz o szerokości 400px na wyświetlaczu o DPR równym 2, każdy piksel logiczny jest renderowany w 4 pikselach fizycznych wyświetlacza: 2 w poziomie i 2 w pionie. Obraz o dużej gęstości nie korzysta z funkcji wyświetlania obrazu. Będzie wyglądał tak samo jak na wyświetlaczu o dr DPR równym 1. Oczywiście wszystko, co zostanie „rysowane” przez mechanizm renderowania przeglądarki – na przykład tekst, kształty CSS lub pliki SVG – zostanie narysowane na wyświetlaczu w wyższej gęstości. Jednak, jak dowiedzieliśmy się z formatów obrazów i kompresji, obrazy rastrowe to stałe identyfikatory pikseli. Choć nie zawsze jest to oczywiste, obraz rastrowy skalowany tak, by pasował do wyświetlacza o większej gęstości, będzie wyglądał poniżej oczekiwań.

Aby uniknąć skalowania w górę, renderowany obraz musi mieć rzeczywistą szerokość co najmniej 800 pikseli. Po zmniejszeniu w celu dopasowania do przestrzeni w układzie o szerokości 400 pikseli logicznych źródło obrazu o rozdzielczości 800 pikseli ma dwukrotnie większą gęstość pikseli. Na wyświetlaczu o DPR równym 2 obraz wygląda ładnie i wyraźnie.

Zbliżenie na płatek kwiatu wykazujący rozbieżność.

Wyświetlacz o długości DPR o wartości 1 nie może wykorzystać zwiększonej gęstości obrazu, dlatego zostanie przeskalowany w dół, aby pasował do wyświetlacza. Jak wiesz, obniżony obraz będzie wyglądał dobrze. Na wyświetlaczu o niskiej gęstości obraz odpowiedni do wyświetlania w wyższej gęstości będzie wyglądać tak samo jak każdy inny obraz o niskiej gęstości.

Jak dowiedzieliśmy się z sekcji Obrazy i skuteczność, użytkownik korzystający z wyświetlacza o małej gęstości, który wyświetla źródło obrazu pomniejszone do 400px, potrzebuje tylko źródła o wbudowanej szerokości 400px. Znacznie większy obraz będzie odpowiedni dla wszystkich użytkowników, natomiast duży obraz w wysokiej rozdzielczości wyrenderowany na małym ekranie o niskiej gęstości będzie wyglądać jak każdy inny mały obraz o niskiej gęstości, ale będzie znacznie wolniejszy.

Jak się domyślasz, urządzenia mobilne z DPR równym 1 rzadko się zdarzają, choć nadal powszechnie pojawiają się w przeglądarkach na komputerach. Z danych udostępnianych przez Matta Hobbsa wynika, że około 18% sesji przeglądania GOV.UK z listopada 2022 r. zgłasza deklarację wskazaną w kolumnie DPR równą 1. Obrazy w wysokiej rozdzielczości wyglądają tak, jak mogliby się tego spodziewać, ale wiążą się ze znacznie większą przepustowością i kosztami przetwarzania. Szczególnie niepokoi to użytkowników starszych i mniej wydajnych urządzeń, które nadal mają wyświetlacze o małej gęstości.

Używanie srcset daje pewność, że tylko urządzenia z wyświetlaczami o wysokiej rozdzielczości otrzymują źródła obrazów o wystarczająco dużej rozdzielczości, które zapewniają ostry obraz przy takim samym koszcie łącza, nie ponosząc przy tym takich samych kosztów przepustowości.

Atrybut srcset wskazuje co najmniej jeden kandydat do renderowania obrazu (rozdzielany przecinkami). Każdy kandydat składa się z 2 elementów: adresu URL (tak jak w przypadku src) oraz składni, która opisuje źródło obrazu. Każdy kandydat w funkcji srcset jest opisywany przez jego wbudowaną szerokość („składnię w”) lub zamierzoną gęstość („składnię x”).

Składnia x to skrót od wyrażenia „to źródło jest odpowiednie na potrzeby wyświetlacza o takim gęstości”. Ciąg znaków 2x, po którym następuje ciąg znaków, jest odpowiedni dla wyświetlacza o DPR o wartości 2.

<img src="low-density.jpg" srcset="double-density.jpg 2x" alt="...">

Przeglądarki obsługujące dyrektywę srcset zobaczą 2 przykłady: double-density.jpg, który 2x opisuje jako odpowiedni do wyświetlania z drogą DPR równą 2, i low-density.jpg w atrybucie src. Jeśli w polu srcset nie znajdzie się żadna odpowiednia propozycja, W przypadku przeglądarek, które nie obsługują srcset, atrybut i jego zawartość zostaną zignorowane – jak zwykle zostanie zażądana zawartość pola src.

Wartości podane w atrybucie srcset łatwo pomylić z instrukcjami. Tag 2x informuje przeglądarkę, że powiązany plik źródłowy nadaje się do użycia na wyświetlaczu z DPR równym 2, czyli informacją o samym źródle. Nie informuje przeglądarkę o sposobie korzystania ze źródła, tylko wskazuje sposób jego użycia. To subtelna, ale ważna różnica: jest to obraz o podwójnej gęstości, a nie obraz do zastosowania na wyświetlaczu o podwójnej gęstości.

Różnica między składnią mówiącą „to źródło jest odpowiednie dla wyświetlaczy 2x” a składnią „Użyj tego źródła na wyświetlaczach 2x” jest niewielka, ale gęstość wyświetlania to tylko jeden z wielu powiązanych czynników, na podstawie których przeglądarka decyduje o wyświetlaniu witryny kandydującej do renderowania. Tylko niektóre z nich są znane. Pojedynczo możesz ustalić, czy użytkownik za pomocą zapytania o multimedia prefers-reduced-data włączył w przeglądarce ustawienie oszczędzające przepustowość, i w ten sposób włączyć dla użytkowników obrazy o małej gęstości niezależnie od gęstości ekranu. Jednak jeśli nie wdrożysz ich spójnie w każdej witrynie przez każdego dewelopera, nie będzie to zbyt przydatne dla użytkownika. W jednej witrynie mogą być przestrzegane ich upodobania, a w drugiej natknąć się na ścianę z obrazami zasłaniającą przepustowość.

Celowo niejasny algorytm wyboru zasobów używany w srcset/sizes pozwala przeglądarkom na wybór obrazów o mniejszej gęstości ze spadkami przepustowości lub na podstawie ograniczenia wykorzystania danych, bez uwzględniania przez nas odpowiedzialności za sposób, czas i wartość progową. Nie ma sensu brać odpowiedzialności i dodatkowej pracy, bo przeglądarka jest w stanie lepiej Ci pomóc.

Opisywanie szerokości za pomocą parametru w

srcset akceptuje drugi typ deskryptora dla kandydatów źródłowych obrazów. Jest znacznie bardziej wydajny i dla naszych celów znacznie łatwiejszy do zrozumienia. Zamiast oznaczać kandydata, który ma odpowiednie wymiary przy danej gęstości wyświetlania, składnia w opisuje nieodłączną szerokość każdego kandydatu. Również w przypadku każdej propozycji każdy element jest identycznie zapisany pod względem wymiarów: te same treści, te same przycięcia i ten sam format obrazu. W tym przypadku przeglądarka użytkownika powinna wybrać 1 z 2 elementów: „small.jpg” (źródło ma automatycznie szerokość 600 pikseli) i large.jpg, czyli źródło o średniej szerokości 1200 pikseli.

srcset="small.jpg 600w, large.jpg 1200w"

Nie informuje to przeglądarki, co ma zrobić na podstawie tych informacji – przekazuje tylko listę kandydatów do wyświetlenia obrazu. Przed podjęciem decyzji o źródle do wyrenderowania musisz podać mu trochę więcej informacji: opis sposobu renderowania obrazu na stronie. Aby to zrobić, użyj atrybutu sizes.

Opisuję użycie w usłudze sizes

Przeglądarki są niezwykle wydajne podczas przesyłania obrazów. Żądania zasobów graficznych są wysyłane na długo przed żądaniami arkuszy stylów lub JavaScriptu – często nawet przed pełnym przeanalizowaniem znaczników. Gdy przeglądarka wysyła takie żądania, nie ma żadnych informacji o samej stronie poza znacznikami – może nie wysłać jeszcze żądań dotyczących zewnętrznych arkuszy stylów, a co dopiero je stosować. W momencie, gdy przeglądarka analizuje znaczniki i rozpoczyna wysyłanie żądań zewnętrznych, posiada tylko informacje na poziomie przeglądarki: rozmiar widocznego obszaru, gęstość pikseli wyświetlacza użytkownika, preferencje użytkownika itd.

Nie mówi nam to nic o tym, jak ma być renderowany obraz w układzie strony – nie może on nawet używać widocznego obszaru jako punktu orientacyjnego dla górnej granicy rozmiaru img, ponieważ może on zajmować kontener przewijany w poziomie. Musimy więc przekazać te informacje przeglądarce za pomocą znaczników. To wszystko, co będziemy mogli wykorzystać w przypadku tych żądań.

Podobnie jak srcset, sizes ma udostępniać informacje o obrazie od razu po przeanalizowaniu znaczników. Tak jak atrybut srcset to skrót od wyrażenia „tu są pliki źródłowe i ich nieodłączne rozmiary”, tak samo atrybut sizes jest skrócony, czyli „rozmiar wyrenderowanego obrazu w układzie”. Sposób opisania obrazu zależy od widocznego obszaru – ponownie jest to jedyna informacja o układzie, jaką przeglądarka otrzymuje podczas wysyłania żądania grafiki.

Na druku może się to wydawać nieco skomplikowane, ale znacznie łatwiej je zrozumieć w praktyce:

<img
 sizes="80vw"
 srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
 src="fallback.jpg"
 alt="...">

Ta wartość sizes informuje przeglądarkę, że przestrzeń w naszym układzie, którą zajmuje img, ma szerokość 80vw–80% widocznego obszaru. Pamiętaj, że to nie instrukcja, tylko opis rozmiaru obrazu w układzie strony. Nie informuje to „obraz zajmujący 80% widocznego obszaru”, ale „po wyrenderowaniu strony ten obraz będzie zajmować 80% widocznego obszaru”.

Twoja praca jest wykonywane przez programistę. Udało Ci się dokładnie opisać listę kandydujących źródeł w elemencie srcset oraz szerokość obrazu w sizes. Podobnie jak w przypadku składni x w srcset, reszta zależy od przeglądarki.

Aby jednak w pełni zrozumieć, w jaki sposób wykorzystywane są te informacje, omówimy teraz decyzje podejmowane przez przeglądarkę użytkownika po napotkaniu tych znaczników:

Przeglądarka otrzymała informację, że ten obraz zajmie 80% dostępnego obszaru. Gdybyśmy wyrenderowali ten element img na urządzeniu z widocznym obszarem o szerokości 1000 pikseli, zajmie 800 pikseli. Przeglądarka wybierze tę wartość i podzielisz ją przez szerokość każdego kandydata na źródło obrazów, który wskażemy w zasadzie srcset. Najmniejsze źródło ma naturalny rozmiar 600 pikseli, czyli: 600÷800=0,75. Nasz obraz ma szerokość 1200 pikseli: 1200 ÷ 800=1,5. Nasz największy obraz ma szerokość 2000 pikseli: 2000 ÷ 800=2,5.

Wyniki tych obliczeń (.75, 1.5 i 2.5) to w praktyce opcje DPR dostosowane do rozmiaru widocznego obszaru użytkownika. Ponieważ przeglądarka ma również informacje o układzie interfejsu użytkownika, podejmuje szereg decyzji:

Przy tym rozmiarze widocznego obszaru propozycja small.jpg jest odrzucana niezależnie od gęstości interfejsu użytkownika. Przy obliczonej wartości DPR niższej niż 1 to źródło wymaga przeskalowania w przypadku dowolnego użytkownika, więc nie jest odpowiednie. W przypadku urządzenia z DPR równym 1 medium.jpg zapewnia najbliższe dopasowanie – to źródło jest odpowiednie do wyświetlania w danym DPR wynoszącym 1.5, więc jest ono nieco większe niż to konieczne. Pamiętaj jednak, że zmniejszanie budżetu jest procesem płynnym pod względem wizualnym. W przypadku urządzenia z DPR równym 2 wartość large.jpg jest najbliższa dopasowania, więc zostaje wybrany.

Jeśli ten sam obraz zostanie wyrenderowany w widocznym obszarze o szerokości 600 pikseli, matematyka będzie wyglądać zupełnie inaczej: 80vw ma teraz 480 pikseli. Po podzieleniu przez tę szerokość szerokości źródeł otrzymujemy 1.25, 2.5 i 4.1666666667. Przy tym rozmiarze widocznego obszaru atrybut small.jpg zostanie wybrany na 1-krotnym urządzeniu, a medium.jpg – na 2 urządzeniach.

Obraz będzie wyglądać tak samo w każdym z tych kontekstów przeglądania: wszystkie pliki źródłowe są dokładnie takie same niezależnie od swoich wymiarów, a każdy obraz jest renderowany z taką ostrością, na jaką pozwala gęstość interfejsu użytkownika. Jednak zamiast wyświetlać large.jpg każdemu użytkownikowi, aby dostosować się do największych widocznych obszarów i wyświetlaczy o największej gęstości, zawsze zobaczą oni najmniejszą odpowiednią propozycję. Dzięki składni opisowej zamiast narzuconej nie musisz ręcznie ustawiać punktów przerwania ani brać pod uwagę przyszłych widocznych obszarów i DPR – wystarczy przekazać przeglądarce odpowiednie informacje i umożliwić jej określenie odpowiedzi za Ciebie.

Wartość sizes jest określana względem widocznego obszaru i całkowicie niezależnej od układu strony, więc dodaje warstwy komplikacji. Rzadko się zdarza, aby obrazy zajmowały tylko procent widocznego obszaru, bez marginesów o stałej szerokości, dopełnienia czy wpływu z innych elementów strony. Szerokość obrazu jest często potrzebna do podawania kombinacji jednostek, wartości procentowych, em, px itd.

Możesz tu użyć właściwości calc() – dowolna przeglądarka z natywną obsługą obrazów elastycznych będzie też obsługiwać calc(), co pozwala nam łączyć i dopasowywać jednostki CSS, np. obraz zajmujący pełną szerokość widocznego obszaru użytkownika pomniejszony o 1em margines po obu stronach:

<img
    sizes="calc(100vw-2em)"
    srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w, x-large.jpg 2400w"
    src="fallback.jpg"
    alt="...">

Opis punktów przerwania

Jeśli poświęcasz dużo czasu na pracę z układami elastycznymi, prawdopodobnie zauważysz, że w tych przykładach brakuje miejsca: przestrzeń zajmowana przez obraz w układzie z dużym prawdopodobieństwem zmieni się w punktach przerwania układu. W takim przypadku musisz przekazać do przeglądarki trochę więcej informacji: sizes akceptuje rozdzielone przecinkami zbiór kandydatów o wyrenderowanym rozmiarze, tak jak srcset akceptuje kandydatów jako źródła obrazu rozdzielone przecinkami. Warunki te korzystają ze znanej składni zapytań o media. Ta składnia jest dopasowywana w pierwszej kolejności: gdy tylko zostanie dopasowany warunek mediów, przeglądarka przestaje analizować atrybut sizes i stosowana jest określona wartość.

Załóżmy, że masz obraz, który ma zajmować 80% widocznego obszaru i minus 1 em dopełnienia po każdej stronie w widocznych obszarach powyżej 1200 pikseli. W mniejszych obszarach zajmuje całą szerokość widocznego obszaru.

  <img
     sizes="(min-width: 1200px) calc(80vw - 2em), 100vw"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Jeśli widoczny obszar jest większy niż 1200 pikseli, calc(80vw - 2em) opisuje szerokość obrazu w naszym układzie. Jeśli warunek (min-width: 1200px) nie jest zgodny, przeglądarka przechodzi do następnej wartości. Z tą wartością nie jest powiązany żaden konkretny warunek mediów, więc domyślna wartość to 100vw. Gdyby udało Ci się napisać ten atrybut sizes za pomocą zapytań o media max-width:

  <img
     sizes="(max-width: 1200px) 100vw, calc(80vw - 2em)"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Prostym językiem: „Czy (max-width: 1200px) pasuje? Jeśli nie, przejdź dalej. Kolejna wartość – calc(80vw - 2em) – nie ma warunku kwalifikującego, więc jest wybrana.

Teraz, gdy przekażesz w przeglądarce wszystkie informacje o elemencie img – o potencjalnych źródłach, nieodłącznych szerokościach i sposobie, w jaki chcesz zaprezentować obraz użytkownikowi, przeglądarka używa zestawu reguł, które określają, co zrobić z tymi informacjami. Jeśli to brzmi niejednoznacznie, to dlatego, że tak właśnie jest. Algorytm wyboru źródła zakodowany w specyfikacji HTML jest wyraźnie niejasny w sprawie wyboru źródła. Po przeanalizowaniu źródeł, ich deskryptorów i sposobu renderowania obrazu przeglądarka może robić, cokolwiek zechce – nie wiadomo, które źródło wybierze.

Składnia, która brzmi „użyj tego źródła na wyświetlaczu o wysokiej rozdzielczości” jest przewidywalna, ale nie rozwiąże podstawowego problemu z obrazami w elastycznym układzie, który polega na oszczędzaniu przepustowości. Gęstość pikseli na ekranie jest tylko racjonalnie związana z szybkością połączenia z internetem, o ile w ogóle to jest z nią. Jeśli korzystasz z laptopa najwyższej klasy, ale przeglądasz internet za pomocą połączenia z pomiarem użycia danych, połączenia z telefonem lub niestabilnego połączenia Wi-Fi w samolocie, możesz zrezygnować ze źródeł obrazów w wysokiej rozdzielczości, niezależnie od jakości wyświetlacza.

Pozostawienie ostatecznej decyzji przeglądarce pozwala uzyskać znacznie lepsze wyniki, niż byłoby to możliwe przy zastosowaniu ściśle określonej składni. Na przykład: w większości przeglądarek element img korzystający ze składni srcset lub sizes nigdy nie będzie przeszkodzić w żądaniu źródła o mniejszych wymiarach niż ten, który użytkownik już ma w pamięci podręcznej przeglądarki. Jaki sens miałoby przesłanie nowego żądania źródła, które wyglądałoby identycznie, gdyby przeglądarka może płynnie przeskalować źródło obrazu w dół? Jeśli jednak użytkownik skaluje widoczny obszar do momentu, w którym potrzebny jest nowy obraz, by uniknąć skalowania, to żądanie zostanie zrealizowane, więc wszystko będzie wyglądać zgodnie z oczekiwaniami.

Brak wyraźnej kontroli może wydawać się nieco przerażający, ale ponieważ używasz plików źródłowych z identyczną treścią, prawdopodobieństwo, że użytkownicy będą mieć problemy z „zepsuciem” środowiska, nie zawsze będzie takie samo jak w przypadku src z jednym źródłem, niezależnie od decyzji podejmowanych przez przeglądarkę.

Używasz sizes i srcset

To dużo informacji – zarówno dla Ciebie, jak i dla czytelników, i dla przeglądarki. Zarówno srcset, jak i sizes mają gęstą składnię i opisują szokującą ilość informacji przy użyciu stosunkowo kilku znaków. Oznacza to, że z założenia są one mniej obszerne i łatwiejsze do analizy przez człowieka, co może utrudniać przeglądarce ich analizę. Im większa złożoność ciągu, tym większe ryzyko błędów parsera lub niezamierzonych różnic w działaniu między przeglądarkami. Ma to jednak pewne zalety: składnia, która jest łatwiejsza do odczytania przez maszyny, jest łatwiejsza do napisania przez użytkownika.

srcset to oczywista zasada dotycząca automatyzacji. Rzadko się zdarza, że ręcznie tworzysz wiele wersji obrazów na potrzeby środowiska produkcyjnego. Zamiast tego automatyzujesz proces za pomocą kreatora zadań, takiego jak Gulp, narzędzie do tworzenia pakietów takiego jak Webpack, zewnętrznej sieci CDN, takiej jak Cloudinary, lub funkcji wbudowanej w wybrany przez Ciebie system CMS. Mając wystarczającą ilość informacji, aby wygenerować nasze źródła, system powinien dysponować taką ilością danych, aby zapisać je w prawidłowym atrybucie srcset.

Automatyzacja sizes jest trochę trudniejsza do automatyzacji. Jedynym sposobem, w jaki system może obliczyć rozmiar obrazu w wyrenderowanym układzie, jest wyrenderowanie układu. Na szczęście pojawiły się nowe narzędzia dla programistów, które pozwalają całkowicie eliminować proces pisania atrybutów sizes odręcznie. Takie narzędzia są niemożliwe do porównania z ręczną skutecznością. Jest to na przykład fragment kodu respImageLint, który służy do sprawdzania dokładności atrybutów sizes i dostarczania sugestii poprawy. Projekt Leniwe rozmiary obniża wydajność, opóźniając obsługę żądań obrazów do momentu utworzenia układu, w którym JavaScript będzie mógł wygenerować wartości sizes. Jeśli używasz platformy renderowania po stronie klienta, takiej jak React czy Vue, masz do dyspozycji wiele rozwiązań do tworzenia i generowania atrybutów srcset oraz sizes. Omówimy je szerzej w sekcji CMS i platformy.