Tworzenie efektywnego komponentu obrazu

Komponent obrazu zawiera sprawdzone metody zwiększania wydajności i stanowi gotowe rozwiązanie do optymalizacji obrazów.

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Castle
Alex Castle

Obrazy są częstym źródłem wąskich gardła wydajności aplikacji internetowych i głównym obszarem optymalizacji. Niezoptymalizowane obrazy tworzą zmienność strony i przy 90 centylu stanowią obecnie ponad 70% całkowitej wagi strony (w bajtach). Różne sposoby optymalizacji obrazów wymagają inteligentnego „komponentu obrazu” z wbudowanymi rozwiązaniami zwiększającymi wydajność.

Zespół Aurora współpracował z Next.js, aby stworzyć jeden taki komponent. Celem było stworzenie zoptymalizowanego szablonu obrazu, który programiści stron internetowych mogli w większym stopniu dostosować. Komponent stanowi dobry model i wyznacza standardy tworzenia komponentów obrazów w innych platformach, systemach zarządzania treścią (CMS) i stosach technologicznych. Nawiązaliśmy współpracę z podobnym komponentem dla Nuxt.js i pracujemy z firmą Angular nad optymalizacją obrazów w kolejnych wersjach. W tym poście opisujemy, jak zaprojektowaliśmy komponent Obraz Next.js, a także czego się po drodze nauczyliśmy.

Komponent graficzny jako rozszerzenie obrazów

Możliwości i problemy z optymalizacją obrazów

Obrazy wpływają nie tylko na skuteczność, ale także na działalność. Liczba obrazów na stronie stanowiła drugi największy wskaźnik prognozujący konwersje użytkowników odwiedzających witryny. Sesje, podczas których użytkownicy dokonali konwersji, miały o 38% mniej obrazów niż sesje, podczas których nie dokonali konwersji. W ramach kontroli sprawdzonych metod Lighthouse udostępnia wiele możliwości optymalizacji obrazów i poprawy wskaźników internetowych. Oto niektóre typowe obszary, w których obrazy mogą wpływać na podstawowe wskaźniki internetowe, a także wrażenia użytkowników.

Nieodpowiednie obrazy wpływają na CLS

Obrazy bez określonego rozmiaru mogą powodować niestabilność układu i przyczyniać się do wysokiego skumulowanego przesunięcia układu (CLS). Ustawienie atrybutów width i height w elementach img może zapobiec przesunięciu układu. Na przykład:

<img src="flower.jpg" width="360" height="240">

Szerokość i wysokość należy ustawić tak, aby współczynnik proporcji renderowanego obrazu był jak najbardziej zbliżony do naturalnego współczynnika proporcji. Znaczna różnica w współczynniku proporcji może spowodować zniekształcenie obrazu. Względnie nowa właściwość, która umożliwia określenie współczynnika proporcji w CSS, może pomóc w elastycznym dostosowaniu rozmiaru obrazów, zapobiegając jednocześnie CLS.

Duże obrazy mogą negatywnie wpływać na LCP

Im większy rozmiar obrazu, tym dłużej trwa jego pobieranie. Duży obraz może być „głównym” obrazem strony lub najważniejszym elementem w widocznym obszarze, który wywołuje największe wyrenderowanie treści (LCP). Obraz, który stanowi część kluczowej treści, a jego pobieranie długo trwa, opóźnia LCP.

W wielu przypadkach deweloperzy mogą zmniejszyć rozmiar obrazów dzięki lepszej kompresji i zastosowaniu obrazów elastycznych. Atrybuty srcset i sizes elementu <img> pomagają w dostarczaniu plików graficznych o różnych rozmiarach. Przeglądarka może wtedy wybrać właściwą opcję w zależności od rozmiaru ekranu i rozdzielczości.

Słaba kompresja obrazu może negatywnie wpłynąć na LCP

Nowoczesne formaty obrazów, takie jak AVIF czy WebP, oferują lepszą kompresję niż powszechnie używane formaty JPEG i PNG. Lepsza kompresja zmniejsza rozmiar pliku o 25–50% w przypadku tej samej jakości obrazu. Umożliwia to szybsze pobieranie danych przy mniejszym zużyciu danych. Aplikacja powinna wyświetlać nowoczesne formaty obrazów w przeglądarkach, które je obsługują.

Wczytywanie niepotrzebnych obrazów negatywnie wpływa na LCP

Obrazy w części strony widocznej po przewinięciu lub poza widocznym obszarem nie są wyświetlane podczas wczytywania strony. Można je odroczyć, aby nie przyczyniły się do wzrostu LCP. Leniwe ładowanie pozwala wczytywać takie obrazy później, gdy użytkownik przewija stronę w jego stronę.

Wyzwania związane z optymalizacją

Zespoły mogą ocenić koszty wydajności wynikające z powyższych problemów i wdrożyć sprawdzone metody ich rozwiązania. Jednak często nie zdarza się to w praktyce, a nieefektywne obrazy nadal spowalniają działanie internetu. Możliwe przyczyny:

  • Priorytety: deweloperzy stron internetowych zwykle skupiają się na kodzie, JavaScripcie i optymalizacji danych. W związku z tym mogą nie wiedzieć o problemach z obrazami lub o tym, jak je zoptymalizować. Obrazy utworzone przez projektantów lub przesłane przez użytkowników mogą nie znajdować się na wysokiej liście priorytetów.
  • Gotowe rozwiązanie: nawet jeśli deweloperzy są świadomi niuansów optymalizacji obrazów, ich zniechęcaniem może być brak kompleksowego rozwiązania do obsługi platformy lub stosu technologicznego.
  • Dynamiczne obrazy: oprócz obrazów statycznych, które są częścią aplikacji, obrazy dynamiczne są przesyłane przez użytkowników lub pobierane z zewnętrznych baz danych bądź systemów CMS. Określenie rozmiaru takich obrazów, jeśli źródło obrazu jest dynamiczne, może być trudne.
  • Przeciążenie znaczników: rozwiązania uwzględniania rozmiaru obrazu lub atrybutu srcset w przypadku różnych rozmiarów wymagają dodatkowych znaczników w przypadku każdego obrazu, co może być uciążliwe. Atrybut srcset został wprowadzony w 2014 roku, ale obecnie jest używany tylko przez 26,5% witryn. Gdy używasz srcset, deweloperzy muszą tworzyć obrazy w różnych rozmiarach. Narzędzia takie jak just-gimme-an-img mogą okazać się przydatne, ale trzeba ich używać ręcznie w przypadku każdego obrazu.
  • Obsługa przeglądarek: nowoczesne formaty obrazów, takie jak AVIF i WebP, tworzą mniejsze pliki graficzne, ale wymagają specjalnej obsługi w przeglądarkach, które ich nie obsługują. Aby obrazy były wyświetlane we wszystkich przeglądarkach, deweloperzy muszą stosować strategie takie jak negocjowanie treści lub element <picture>.
  • Leniwe ładowanie widżetów: dostępnych jest wiele technik i bibliotek, które umożliwiają skorzystanie z leniwego ładowania obrazów w części strony widocznej po przewinięciu. Wybór najlepszej reklamy może być wyzwaniem. Deweloperzy mogą też nie znać najlepszej odległości od obszaru strony widocznej po przewinięciu, by wczytywać obrazy odroczone. Różne rozmiary widocznego obszaru na urządzeniach mogą dodatkowo komplikować tę sytuację.
  • Zmiany na rynku: w miarę jak przeglądarki zaczną obsługiwać nowe funkcje HTML lub CSS w celu zwiększenia wydajności, deweloperom może być trudno ocenić każdą z nich. Na przykład w Chrome wprowadzamy funkcję Priorytet pobierania jako wersję próbną origin. Można go użyć, aby zwiększyć priorytet konkretnych obrazów na stronie. Ogólnie rzecz biorąc, deweloperzy będą łatwiej, jeśli takie ulepszenia zostałyby ocenione i zaimplementowane na poziomie komponentu.

Komponent obrazu jako rozwiązanie

Możliwości optymalizacji obrazów i wyzwania związane z wdrażaniem ich indywidualnie w każdej aplikacji skłoniły nas do pomysłu na komponent obrazu. Komponent obrazu może zawierać i egzekwować sprawdzone metody. Zastąpienie elementu <img> komponentem graficznym pozwala deweloperom lepiej rozwiązać problemy z wydajnością obrazów.

W ciągu ostatniego roku współpracowaliśmy z platformą Next.js, aby zaprojektować i implement komponent graficzny. Można go używać jako zamiennika dotychczasowych elementów <img> w aplikacjach Next.js w podany niżej sposób.

// Before with <img> element:
function Logo() {
  return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}

// After with image component:
import Image from 'next/image'

function Logo() {
  return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}

Stara się on ogólnie rozwiązywać problemy dotyczące obrazów, korzystając z bogatego zestawu funkcji i zasad. Zawiera też opcje, które pozwalają deweloperom dostosowywać go do różnych wymagań dotyczących obrazów.

Ochrona przed przesunięciami układu

Jak już wspomnieliśmy, obrazy o niewielkim rozmiarze powodują przesunięcia układu i mają wpływ na CLS. Jeśli korzystasz z komponentu Obraz Next.js, programiści muszą podać rozmiar obrazu za pomocą atrybutów width i height, aby zapobiec ewentualnym zmianom układu. Jeśli rozmiar jest nieznany, deweloperzy muszą podać layout=fill, aby wyświetlać obraz bez rozmiaru, który znajduje się w kontenerze o określonym rozmiarze. Możesz też użyć importu obrazów statycznych w celu pobrania rzeczywistego rozmiaru obrazu z dysku twardego podczas tworzenia i umieszczenia go na zdjęciu.

// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />

// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />

// Image component with image import
import Image from 'next/image'
import logo from './logo.png'

function Logo() {
  return <Image src={logo} alt="logo" />
}

Ponieważ deweloperzy nie mogą używać komponentu Obraz bez określonego rozmiaru, poświęcają czas na dostosowanie rozmiaru obrazu i zapobieżenie zmianom układu.

Ułatwianie reagowania

Aby obrazy były elastyczne na różnych urządzeniach, deweloperzy muszą ustawić atrybuty srcset i sizes w elemencie <img>. Chcieliśmy ograniczyć nakład pracy dzięki komponentowi Obraz. Zaprojektowaliśmy komponent Obraz Next.js, aby ustawiać wartości atrybutów tylko raz w danej aplikacji. Stosujemy je do wszystkich wystąpień komponentu Obraz zależnie od trybu układu. Opracowaliśmy 3-częściowe rozwiązanie:

  1. Właściwość deviceSizes: ta właściwość może służyć do jednorazowego konfigurowania punktów przerwania na podstawie urządzeń wspólnych dla użytkowników aplikacji. Wartości domyślne punktów przerwania znajdują się w pliku konfiguracyjnym.
  2. imageSizes: to także konfigurowalna właściwość służąca do pobierania rozmiarów obrazów odpowiadających punktom przerwania rozmiaru urządzenia.
  3. layout na każdym zdjęciu: ten atrybut określa, jak należy używać właściwości deviceSizes i imageSizes w przypadku każdego zdjęcia. Obsługiwane wartości trybu układu to fixed, fill, intrinsic i responsive

Gdy żądanie obrazu jest wysyłane przy użyciu trybów układu elastycznego lub wypełniania, Next.js identyfikuje obraz do wyświetlenia na podstawie rozmiaru urządzenia, z którego pochodzi żądanie strony, i odpowiednio ustawia w nim parametry srcset oraz sizes.

Porównanie poniżej pokazuje, jak za pomocą trybu układu można kontrolować rozmiar obrazu na różnych ekranach. Wykorzystaliśmy obraz demonstracyjny udostępniony w dokumentach Next.js do wyświetlenia na telefonie i standardowym laptopie.

Ekran laptopa Ekran telefonu
Układ = Wewnętrzny: skaluje w dół, by dopasować do szerokości kontenera na mniejszych widocznych obszarach. Nie skaluje się w górę poza wewnętrzny rozmiar obrazu na większym widocznym obszarze. Szerokość kontenera wynosi 100%
Obraz gór wyświetlany bez zmian Obraz gór przeskalowany w dół
Układ = Naprawiony: obraz nie jest elastyczny. Szerokość i wysokość są stałe, podobnie jak w przypadku elementu „”, niezależnie od urządzenia, na którym jest renderowany.
Obraz gór wyświetlany bez zmian Obraz przedstawiający góry w stanie nie pasuje do ekranu
Układ = Elastyczny – skaluj w dół lub w górę w zależności od szerokości kontenera w różnych widocznych obszarach z zachowaniem współczynnika proporcji.
Obraz gór przeskalowany w górę, aby pasował do ekranu Obraz gór pomniejszony, by pasował do ekranu
Układ = Wypełnienie: szerokość i wysokość rozciągnięte, by wypełnić kontener nadrzędny. (Nadrzędny „
szerokość jest w tym przykładzie ustawiona na 300 x 500)
Obraz gór wyrenderowany do rozmiaru 300 x 500 Obraz gór wyrenderowany do rozmiaru 300 x 500
Obrazy renderowane na potrzeby różnych układów

Udostępnianie wbudowanego leniwego ładowania

Komponent Obraz domyślnie udostępnia wbudowane, wydajne rozwiązanie leniwego ładowania. W przypadku użycia elementu <img> istnieje kilka opcji natywnych leniwego ładowania, ale wszystkie mają wady utrudniające korzystanie z nich. Deweloper może zastosować jedną z tych metod leniwego ładowania:

  • Określ atrybut loading: ten atrybut jest łatwy do wdrożenia, ale obecnie nieobsługiwany w niektórych przeglądarkach.
  • Użycie interfejsu Intersection Observer API: stworzenie niestandardowego rozwiązania do leniwego ładowania wymaga przemyślanego projektu i wdrożenia. Deweloperzy nie zawsze mają na to czas.
  • Importowanie biblioteki zewnętrznej do obrazów z leniwym ładowaniem: ocena i integracja odpowiedniej biblioteki zewnętrznej na potrzeby leniwego ładowania mogą wymagać dodatkowych nakładów pracy.

W komponencie Obraz Next.js wczytywanie jest domyślnie ustawione na "lazy". Leniwe ładowanie jest implementowane za pomocą narzędzia Intersection Observer (Obserwatorium), które jest dostępne w większości nowoczesnych przeglądarek. Deweloperzy nie muszą nic robić, aby ją włączyć, ale w razie potrzeby mogą ją wyłączyć.

Wstępnie wczytuj ważne obrazy

Elementy LCP są często obrazami, a duże obrazy mogą opóźniać LCP. Warto wstępnie wczytać najważniejsze obrazy, aby przeglądarka mogła szybciej wykryć dany obraz. Gdy używasz elementu <img>, w nagłówku HTML możesz umieścić wskazówkę dotyczącą wstępnego wczytywania w ten sposób.

<link rel="preload" as="image" href="important.png">

Dobrze zaprojektowany komponent obrazu powinien umożliwiać dostosowanie sekwencji wczytywania obrazów niezależnie od używanej platformy. W przypadku komponentu Obraz Next.js programiści mogą wskazać obraz, który nadaje się do wstępnego wczytania, korzystając z atrybutu priority komponentu graficznego.

<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />

Dodanie atrybutu priority upraszcza znaczniki i jest wygodniejsze w użyciu. Programiści komponentów obrazu mogą też zastanowić się nad opcjami heurystyki, by zautomatyzować wstępne wczytywanie obrazów w części strony widocznej na ekranie i spełniających określone kryteria.

Wspieranie wysokiej wydajności hostingu obrazów

Do automatyzacji optymalizacji obrazów zalecamy korzystanie z sieci CDN z obrazami. Obsługują one również nowoczesne formaty obrazów, takie jak WebP i AVIF. Komponent Obraz Next.js domyślnie korzysta z sieci CDN dla obrazów z wykorzystaniem architektury modułu ładowania. Przykład poniżej pokazuje, że narzędzie ładujące pozwala na konfigurację sieci CDN w pliku konfiguracyjnym Next.js.

module.exports = {
  images: {
    loader: 'imgix',
    path: 'https://ImgApp/imgix.net',
  },
}

Dzięki tej konfiguracji deweloperzy mogą używać względnych adresów URL w źródle obrazów, a platforma połączy względny adres URL ze ścieżką CDN, by wygenerować bezwzględny URL. Obsługiwane są popularne sieci CDN z obrazami, takimi jak Imgix, Cloudinary i Akamai. Architektura ta umożliwia korzystanie z usług niestandardowego dostawcy chmury przez wdrożenie w aplikacji niestandardowej funkcji loader.

Obsługa obrazów hostowanych samodzielnie

W niektórych sytuacjach witryny nie mogą korzystać z obrazów CDN. W takich przypadkach komponent obrazu musi obsługiwać obrazy hostowane na własnym serwerze. Komponent obrazu Next.js korzysta z optymalizatora obrazów jako wbudowanego serwera obrazów, który udostępnia interfejs API podobny do CDN. Optymalizator, który jest zainstalowany na serwerze, korzysta z narzędzia Sharp do przekształcania obrazu w środowisku produkcyjnym. Ta biblioteka to dobry wybór dla każdego, kto chce stworzyć własny proces optymalizacji obrazów.

Obsługa wczytywania progresywnego

Ładowanie progresywne to technika pozwalająca utrzymać zainteresowanie użytkowników przez wyświetlanie obrazu zastępczego o znacznie niższej jakości podczas wczytywania samego obrazu. Poprawia to postrzeganą wydajność i poprawia wrażenia użytkownika. Można jej używać w połączeniu z leniwym wczytywaniem w przypadku obrazów w części strony widocznej na ekranie i w części strony widocznej na ekranie.

Komponent Obraz Next.js obsługuje ładowanie progresywne obrazu za pomocą właściwości placeholder. Ten element może służyć jako LQIP (obiekt zastępczy obrazu niskiej jakości), który wyświetla obraz niskiej jakości lub jest rozmyty podczas rzeczywistego wczytywania obrazu.

Wpływ

Po zastosowaniu powyższych optymalizacji udało nam się wdrożyć komponent Obraz Next.js w środowisku produkcyjnym, a także współpracować z innymi stosami technologicznymi nad podobnymi komponentami obrazów.

Podczas migracji starszego frontendu JavaScript do Next.js firma Leboncoin uaktualniła również swój potok obrazów, aby wykorzystywał komponent obrazu Next.js. Na stronie, która została przeniesiona z <img> do następnego obrazu, wskaźnik LCP spadł z 2,4 s do 1,7 s. Łączna liczba bajtów obrazów pobranych ze strony wzrosła z 663 kB do 326 kB (około 100 KB z leniwego ładowania obrazu).

Zdobyte lekcje

Każdy, kto tworzy aplikację Next.js, może skorzystać z komponentu Obraz Next.js do optymalizacji. Jeśli jednak chcesz stworzyć podobne podsumowania skuteczności dla innego platformy lub systemu CMS, poniżej znajdziesz kilka przydatnych wniosków, które mogą Ci się przydać.

Zawory bezpieczeństwa mogą spowodować więcej szkód niż pożytku

We wczesnej wersji komponentu Obraz Next.js udostępniliśmy atrybut unsized, który pozwalał programistom pominąć wymóg dotyczący rozmiaru i używać obrazów o nieokreślonych wymiarach. Uznaliśmy, że będzie to konieczne w sytuacjach, gdy nie da się wcześniej określić wysokości lub szerokości obrazu. Zauważyliśmy jednak, że użytkownicy polecają atrybut unsized na GitHubie jako kompleksowe rozwiązanie problemów z wymaganiami dotyczącymi rozmiaru, nawet wtedy, gdy mogą rozwiązać problem w sposób, który nie pogorszy CLS. Następnie wycofaliśmy atrybut unsized i usunęliśmy go.

Oddziel przydatne problemy od bezsensownych irytujących problemów

Wymóg określenia rozmiaru obrazu jest przykładem „użytecznego” problemu. Ogranicza użycie komponentu, ale w zamian zapewnia niezwykłą wydajność. Użytkownicy bez trudu zaakceptują ograniczenie, jeśli będą mieli jasny obraz potencjalnych korzyści płynących ze skuteczności. Warto więc objaśnić tę kwestię w dokumentacji i innych opublikowanych materiałach dotyczących tego komponentu.

Możesz jednak znaleźć obejścia tego problemu, nie pogarszając wydajności. Na przykład w trakcie prac nad komponentem Obraz Next.js otrzymaliśmy skargi, że wyszukiwanie rozmiarów obrazów przechowywanych lokalnie było denerwujące. Dodaliśmy importy obrazów statycznych, które upraszczają ten proces dzięki automatycznemu pobieraniu wymiarów lokalnych obrazów podczas kompilacji za pomocą wtyczki Babel.

Osiągnij równowagę między udogodnieniami a optymalizacją skuteczności.

Jeśli komponent graficzny nie działa tylko w sposób, który utrudnia użytkownikom korzystanie z niego, deweloperzy raczej nie będą chcieli z niego korzystać. Zauważyliśmy, że chociaż najważniejsze były funkcje związane z wydajnością, takie jak rozmiar obrazu i automatyczne generowanie wartości srcset. Dbając o wygodę programistów, takie jak automatyczne leniwe ładowanie i wbudowane rozmyte obiekty zastępcze, również były źródłem zainteresowania komponentem Obraz Next.js.

Opracuj plan rozwoju funkcji zwiększających ich wdrażanie

Stworzenie rozwiązania, które sprawdzi się w każdej sytuacji, jest bardzo trudne. Zaprojektowanie czegoś, co sprawdzi się w przypadku 75% użytkowników, może być kuszące, a potem poinformować pozostałe 25%, że „w takich przypadkach ten komponent nie jest dla Was”.

W praktyce okazuje się, że ta strategia kłóci się z Twoimi celami jako projektanta komponentów. Chcesz, aby deweloperzy zastosowali Twój komponent, aby czerpać korzyści związane z wydajnością. Trudno jest to zrobić, jeśli część użytkowników nie jest w stanie przeprowadzić migracji i czuje się pominiętych w rozmowie. Raczej będą czuli się rozczarowani, co doprowadzi do negatywnych odczuć, które wpływają na rozpowszechnienie tego rozwiązania.

Warto opracować plan działania komponentu, który obejmie wszystkie uzasadnione przypadki użycia w dłuższej perspektywie. W dokumentacji warto też wyraźnie wskazać, które funkcje nie są obsługiwane i dlaczego, aby zorientować się, jakie problemy ma rozwiązać dany komponent.

Podsumowanie

Wykorzystanie i optymalizacja obrazów jest skomplikowane. Deweloperzy muszą znaleźć równowagę między wydajnością i jakością obrazów, a jednocześnie zadbać o wygodę użytkowników. W ten sposób optymalizacja obrazów jest bardzo kosztownym i skutecznym przedsięwzięciem.

Zamiast za każdym razem, aby każda aplikacja była tworzona na nowo, wymyśliliśmy szablon sprawdzonych metod, którego deweloperzy, platformy i inne stosy technologiczne mogą służyć jako punkt odniesienia przy własnych implementacjach. Takie doświadczenie będzie naprawdę bardzo przydatne, ponieważ obsługujemy inne platformy w zakresie komponentów obrazów.

Komponent Obraz Next.js osiągnął lepsze wyniki w aplikacjach Next.js i w ten sposób poprawił wrażenia użytkowników. Uważamy, że to świetny model, który sprawdzi się w szerszym ekosystemie, i chętnie poznamy opinie deweloperów, którzy chcieliby zastosować ten model w swoich projektach.