Tworzenie efektywnego komponentu obrazu

Komponent z obrazem zawiera sprawdzone metody dotyczące skuteczności i zawiera 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 gardeł w przypadku aplikacji internetowych i głównym obszarem, na którym należy się skupić podczas optymalizacji. Niezoptymalizowane obrazy powstają w wyniku przeładowania strony i stanowią ponad 70% łącznej wagi strony w bajtach w 90th centylu. Wiele sposobów optymalizacji obrazów wymaga inteligentnego „elementu graficznego” z domyślnie wbudowanymi rozwiązaniami dotyczącymi wydajności.

Zespół Aurora współpracował z Next.js, aby stworzyć jeden taki komponent. Celem było utworzenie optymalnego szablonu obrazu, który programiści stron internetowych mogliby dalej dostosowywać. Ten komponent stanowi dobry model i ustala standard tworzenia komponentów obrazów w innych ramach, systemach zarządzania treścią (CMS) i technologiach. Współpracowaliśmy nad podobnym komponentem dla Nuxt.js,a obecnie współpracujemy z Angularem nad optymalizacją obrazów w przyszłych wersjach. W tym poście opisujemy, jak zaprojektowaliśmy komponent Image w Next.js, i przedstawiamy wnioski, które z tego wynikły.

Komponent Obraz jako rozszerzenie obrazów

Problemy i możliwości związane z optymalizacją obrazów

Zdjęcia wpływają nie tylko na skuteczność, ale też na wyniki finansowe. Liczba obrazów na stronie była drugim najlepszym czynnikiem prognostycznym konwersji użytkowników odwiedzających witryny. Sesje, w których użytkownicy dokonali konwersji, miały o 38% mniej obrazów niż sesje, w których użytkownicy nie dokonali konwersji. Lighthouse podaje wiele możliwości optymalizacji obrazów i ulepszania podstawowych wskaźników internetowych w ramach audytu dotyczącego sprawdzonych metod. Poniżej przedstawiamy niektóre typowe obszary, w których obrazy mogą wpływać na podstawowe wskaźniki internetowe oraz wygodę użytkowników.

Obrazy bez rozmiarów pogarszają CLS

Obrazy wyświetlane bez określonego rozmiaru mogą powodować niestabilność układu i wysoki skumulowany przesuw układu (CLS). Aby zapobiec przesunięciom układu, możesz ustawić atrybuty width i height w elementach img. Na przykład:

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

Szerokość i wysokość powinny być ustawione tak, aby współczynnik proporcji wyrenderowanego obrazu był zbliżony do jego naturalnego współczynnika proporcji. Znaczne różnice w współczynniku proporcji mogą spowodować zniekształcenia obrazu. Względnie nowa właściwość, która umożliwia określenie współczynnika proporcji w CSS, pomaga elastycznie dostosowywać rozmiar obrazów, a jednocześnie zapobiega występowaniu CLS.

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

Im większy rozmiar pliku obrazu, tym dłużej trwa jego pobieranie. Duży obraz może być obrazem „hero” strony lub najważniejszym elementem w widocznym obszarze, który powoduje największe wyrenderowanie treści (LCP). Obraz, który jest częścią treści o znaczeniu krytycznym, a jego pobranie trwa długo, spowoduje opóźnienie LCP.

W wielu przypadkach deweloperzy mogą zmniejszyć rozmiary obrazów dzięki lepszej kompresji i użyciu obrazów elastycznych. Atrybuty srcset i sizes elementu <img> pomagają udostępniać pliki obrazów o różnych rozmiarach. Przeglądarka może wybrać odpowiedni obraz w zależności od rozmiaru i rozdzielczości ekranu.

Niewłaściwa kompresja obrazu może pogorszyć LCP

Nowoczesne formaty obrazów, takie jak AVIF czy WebP, mogą zapewniać lepszą kompresję niż powszechnie używane formaty JPEG i PNG. Lepsza kompresja zmniejsza rozmiar pliku w niektórych przypadkach o 25–50% przy zachowaniu tej samej jakości obrazu. Dzięki temu pobieranie odbywa się szybciej i 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 pogarsza LCP

Obrazy znajdujące się w części strony widocznej po przewinięciu lub poza widocznym obszarem nie są wyświetlane użytkownikowi po wczytaniu strony. Można je opóźnić, aby nie wpływały na LCP i nie opóźniały go. Leniwe ładowanie może być używane do wczytywania takich obrazów później, gdy użytkownik przewinie stronę w ich kierunku.

Wyzwania optymalizacyjne

Zespoły mogą ocenić koszty skuteczności wynikające z wymienionych wcześniej problemów i wdrożyć sprawdzone metody ich rozwiązania. W praktyce jednak często tak się nie dzieje, a nieefektywne obrazy nadal spowalniają działanie witryn. Możliwe przyczyny:

  • Priorytety: programiści stron internetowych zwykle koncentrują się na kodzie, JavaScripcie i optymalizacji danych. Dlatego mogą nie zdawać sobie sprawy z problemów z obrazami lub sposobu ich optymalizacji. Obrazy utworzone przez projektantów lub przesłane przez użytkowników mogą nie znajdować się wysoko na liście priorytetów.
  • Gotowe rozwiązanie: nawet jeśli deweloperzy znają niuanse optymalizacji obrazów, brak gotowego, kompleksowego rozwiązania dla ich platformy lub stosu technologicznego może zniechęcać do działania.
  • Obrazy dynamiczne: oprócz obrazów statycznych, które są częścią aplikacji, obrazy dynamiczne są przesyłane przez użytkowników lub pochodzą z zewnętrznych baz danych lub systemów CMS. Określanie rozmiaru takich obrazów, których źródło jest dynamiczne, może być trudne.
  • Przeciążenie znaczników: rozwiązania polegające na dołączaniu rozmiaru obrazu lub atrybutu srcset w przypadku różnych rozmiarów wymagają dodatkowych znaczników do każdego obrazu, co może być uciążliwe. Atrybut srcset został wprowadzony w 2014 roku, ale obecnie używa go tylko 26,5% witryn. Podczas korzystania z srcset deweloperzy muszą tworzyć obrazy w różnych rozmiarach. Narzędzia takie jak just-gimme-an-img mogą się przydać, ale w przypadku każdego zdjęcia trzeba będzie go używać ręcznie.
  • Obsługa w przeglądarkach: nowoczesne formaty obrazów, takie jak AVIF i WebP, tworzą mniejsze pliki obrazów, ale wymagają specjalnego traktowania w przeglądarkach, które ich nie obsługują. Deweloperzy muszą stosować strategie takie jak negocjacja treści lub element <picture>, aby obrazy były wyświetlane we wszystkich przeglądarkach.
  • Złożoność leniwego ładowania: istnieje wiele technik i bibliotek umożliwiających stosowanie leniwego ładowania w przypadku obrazów znajdujących się w części strony widocznej po przewinięciu. Wybór najlepszego może być trudny. Deweloperzy mogą też nie znać najlepszej odległości od „załamania”, w której można wczytywać opóźnione obrazy. Sprawę komplikuje jeszcze to, że na różnych urządzeniach widoczny obszar może mieć różne rozmiary.
  • Zmieniający się krajobraz: przeglądarki zaczynają obsługiwać nowe funkcje HTML lub CSS, aby zwiększyć wydajność, ale może być trudno ocenić każdą z nich. Chrome wprowadza na przykład funkcję Priorytet pobierania jako wersję próbną origin. Może służyć do zwiększania priorytetu konkretnych obrazów na stronie. Ogólnie rzecz biorąc, deweloperzy mogliby łatwiej, gdyby takie ulepszenia były oceniane i wdrażane na poziomie komponentu.

Komponent Obraz jako rozwiązanie

Możliwości optymalizacji obrazów i problemy z ich wdrażaniem w poszczególnych aplikacjach skłoniły nas do zaproponowania komponentu obrazów. Komponent obrazu może zawierać sprawdzone metody i je egzekwować. Zastępując element <img> komponentem obrazowym, deweloperzy mogą lepiej rozwiązać problemy ze skutecznością obrazów.

W ciągu ostatniego roku pracowaliśmy przy użyciu platformy Next.js, aby zaprojektować i wdrożyć komponent graficzny. Możesz go używać jako zamiennika dotychczasowych elementów <img> w aplikacjach Next.js w taki 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" />
}

Komponent stara się rozwiązywać ogólne problemy związane z obrazami za pomocą bogatego zestawu funkcji i zasad. Zawiera ona też opcje, które umożliwiają deweloperom dostosowanie jej do różnych wymagań dotyczących obrazów.

Ochrona przed przesunięciami układu

Jak już wspomnieliśmy, obrazy bez wymiarów powodują przesunięcia układu i zwiększają CLS. Aby zapobiec przesunięciom układu, deweloperzy muszą podać rozmiar obrazu za pomocą atrybutów widthheight, gdy używają komponentu Image w Next.js. Jeśli rozmiar jest nieznany, deweloperzy muszą podać wartość layout=fill, aby wyświetlać obraz bez rozmiaru, który znajduje się w kontenerze o określonym rozmiarze. Możesz też zaimportować statyczne obrazy, aby pobrać rozmiar rzeczywistego obrazu na dysku twardym w momencie kompilacji i umieścić go na obrazie.

// 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 rozmiaru, projekt zapewnia im czas na zastanowienie się nad zmianą rozmiaru obrazu i zapobieżenie przesuwaniom układu.

Ułatwianie reagowania

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

  1. Właściwość deviceSizes: ta właściwość może służyć do konfigurowania punktów przełamania jednorazowo na podstawie urządzeń wspólnych dla użytkowników aplikacji. Domyślne wartości punktów przecięcia są zawarte w pliku konfiguracyjnym.
  2. Właściwość imageSizes: to również konfigurowalna właściwość służąca do uzyskiwania rozmiarów obrazów odpowiadających punktom przecięcia rozmiarów urządzeń.
  3. Atrybut layout w każdym obrazie: służy do wskazywania sposobu użycia właściwości deviceSizesimageSizes w przypadku każdego obrazu. Obsługiwane wartości trybu układu to fixed, fill, intrinsic i responsive

Gdy obraz jest żądany w ramach trybów układu elastyczny lub wypełnianie, Next.js określa obraz do wyświetlenia na podstawie rozmiaru urządzenia, które żąda strony, i odpowiednio ustawia na obrazie wartości srcsetsizes.

Z porównania poniżej dowiesz się, jak za pomocą trybu układu kontrolować rozmiar obrazu na różnych ekranach. Użyliśmy obrazu demonstracyjnego udostępnionego w dokumentacji Next.js, który wyświetliliśmy na telefonie i standardowym laptopie.

Ekran laptopa Ekran telefonu
Układ = Właściwy: zmniejsza się, aby dopasować szerokość kontenera do mniejszych widocznych obszarów. Nie powiększa się poza rozmiar obrazu w większym widocznym obszarze. Szerokość kontenera wynosi 100%.
Obraz gór bez zmian Obraz gór pomniejszony
Układ = Statyczny: obraz nie jest responsywny. Szerokość i wysokość są stałe, podobnie jak w przypadku elementu „”, niezależnie od urządzenia, na którym jest renderowany.
Obraz gór bez zmian Obraz gór nie pasuje do ekranu
Układ = elastyczny: skalowanie w dół lub w górę w zależności od szerokości kontenera w różnych widocznych obszarach przy zachowaniu współczynnika proporcji.
Obraz gór powiększony, aby pasował do ekranu Obraz gór, który został pomniejszony, aby pasował do ekranu
Układ = Wypełnij: szerokość i wysokość rozciągnięte, aby wypełnić kontener nadrzędny. (W tym przykładzie szerokość elementu nadrzędnego <div> została ustawiona na 300 × 500)
Obraz gór wyrenderowany w celu dopasowania do rozmiaru 300 × 500 Obraz gór wyrenderowany w celu dopasowania do rozmiaru 300 × 500
Obrazy wyrenderowane dla różnych układów

Wbudowane leniwe ładowanie

Komponent Obraz zapewnia domyślnie wbudowane, wydajne rozwiązanie opóźnionego wczytywania. W przypadku elementu <img> istnieje kilka opcji opóźnionego ładowania, ale wszystkie mają wady, które utrudniają ich używanie. Deweloper może zastosować jedną z tych metod ładowania opóźnionego:

  • Określ atrybut loading: jest on obsługiwany we wszystkich nowoczesnych przeglądarkach.
  • Użyj interfejsu Intersection Observer API: tworzenie niestandardowego rozwiązania opartego na ładowaniu opóźnionym wymaga wysiłku, przemyślanego projektu i wdrożenia. Deweloperzy nie zawsze mają na to czas.
  • Importowanie biblioteki zewnętrznej do wczytywania opóźnionego obrazów: aby ocenić i zintegrować odpowiednią bibliotekę zewnętrzną do wczytywania opóźnionego, może być konieczne podjęcie dodatkowych działań.

W komponencie obrazu Next.js domyślnie ustawiono wczytywanie na "lazy". Leniwe ładowanie jest implementowane za pomocą interfejsu Intersection Observer, który jest dostępny w większości współczesnych przeglądarek. Deweloperzy nie muszą nic robić, aby ją włączyć, ale mogą ją w razie potrzeby wyłączyć.

Wstępnie wczytaj ważne obrazy

Elementy LCP to często obrazy, a duże obrazy mogą opóźniać LCP. Warto wstępnie wczytać kluczowe obrazy, aby przeglądarka mogła je wcześniej wykryć. Jeśli używasz elementu <img>, w nagłówku HTML możesz umieścić podpowiedź dotyczącą wstępnego wczytania, jak pokazano poniżej.

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

Dobrze zaprojektowany komponent z obrazem powinien umożliwiać dostosowanie kolejności wczytywania obrazów niezależnie od używanego frameworka. W przypadku komponentu Image w Next.js deweloperzy mogą wskazać obraz, który nadaje się do wstępnego wczytania, za pomocą atrybutu priority komponentu images.

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

Dodanie atrybutu priority upraszcza znaczniki i ułatwia ich używanie. Deweloperzy komponentów z grafiką mogą też zapoznać się z opcjami stosowania heurystyki do automatycznego wstępnego wczytywania obrazów powyżej pola widzenia na stronie, które spełniają określone kryteria.

Zachęcanie do korzystania z usług hostowania obrazów o wysokiej wydajności

CDN obrazów są zalecane do automatyzacji optymalizacji obrazów. Obsługują też nowoczesne formaty obrazów, takie jak WebP i AVIF. Komponent Image w Next.js domyślnie korzysta z CDN obrazów, używając architektury ładowarki. W tym przykładzie widać, że loader umożliwia skonfigurowanie CDN w pliku konfiguracyjnym Next.js.

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

W takiej konfiguracji deweloperzy mogą używać względnych adresów URL w źródle obrazu, a platforma połączy względny adres URL ze ścieżką CDN w celu wygenerowania bezwzględnego adresu URL. Obsługiwane są popularne sieci CDN z obrazami, takie jak Imgix, Cloudinary i Akamai. Architektura obsługuje korzystanie z niestandardowego dostawcy usług w chmurze dzięki wdrożeniu w aplikacji niestandardowej funkcji loader.

Obsługa obrazów hostowanych samodzielnie

W niektórych przypadkach witryny nie mogą korzystać z CDN obrazów. W takich przypadkach komponent obrazu musi obsługiwać obrazy hostowane lokalnie. Komponent Image w Next.js korzysta z optymalizatora obrazów jako wbudowanego serwera obrazów, który udostępnia interfejs API podobny do CDN. Optymalizator używa Sharp do przekształcania obrazów produkcyjnych, jeśli jest zainstalowany na serwerze. Ta biblioteka jest dobrym wyborem dla każdego, kto chce utworzyć własny proces optymalizacji obrazów.

Obsługa wczytywania progresywnego

Ładowanie progresywne to technika utrzymywania zainteresowania użytkowników, polegająca na wyświetlaniu w czasie wczytywania obrazu zastępczego, który zwykle ma znacznie niższą jakość. Poprawia ona postrzeganą wydajność i zapewnia lepsze wrażenia użytkowników. Można go używać w połączeniu z leniwym ładowaniem obrazów w części strony widocznej po przewinięciu lub w części strony widocznej na ekranie.

Komponent Obraz Next.js obsługuje progresywne ładowanie obrazu za pomocą właściwości placeholder. Może służyć jako obiekt zastępczy obrazu niskiej jakości (LQIP) do wyświetlania rozmytego lub niskiej jakości obrazu podczas wczytywania rzeczywistego obrazu.

Wpływ

Po włączeniu tych wszystkich optymalizacji odnotowaliśmy sukces w produkcji komponentu Next.js Image, a także pracujemy nad podobnymi komponentami technologicznymi przy współpracy z innymi stosami technologicznymi.

Gdy firma Leboncoin przeprowadziła swoją starszą stronę frontową JavaScript na Next.js, zaktualizowała też swój potok obrazów, aby używać komponentu Image w Next.js. Na stronie, która została przeniesiona z poziomu <img> do next/image, czas LCP skrócił się z 2,4 s do 1,7 s. Łączna liczba bajtów pobranych obrazów na stronie zmniejszyła się z 663 KB do 326 KB (z około 100 KB bajtów wczytanych z opóźnieniem).

Zdobyte lekcje

Każdy, kto tworzy aplikację Next.js, może używać komponentu obrazu Next.js do optymalizacji. Jeśli jednak chcesz tworzyć podobne abstrakcje wydajności dla innego frameworku lub systemu CMS, zapoznaj się z kilkoma wnioskami, które mogą Ci się przydać.

Zabezpieczenia mogą przynieść więcej szkody niż pożytku

W pierwszej wersji komponentu Image w ramach Next.js udostępniliśmy atrybut unsized, który umożliwiał deweloperom pominięcie wymagań dotyczących rozmiaru i używanie obrazów o nieokreślonych wymiarach. Uznaliśmy, że jest to konieczne w przypadkach, gdy nie można było z wyprzedzeniem określić wysokości lub szerokości obrazu. Zauważyliśmy jednak, że użytkownicy zalecają w problemach w GitHub atrybucie unsized jako uniwersalne rozwiązanie problemów z wymaganiami dotyczącymi rozmiaru, nawet w przypadkach, gdy można było rozwiązać problem w sposób, który nie pogorszy CLS. Następnie wycofaliśmy i usunęliśmy atrybut unsized.

Oddziel przydatne przeszkody od bezsensownego irytacji

Wymóg określenia rozmiaru obrazu jest przykładem „przydatnych funkcji”. Ogranicza to użycie komponentu, ale zapewnia niezwykłe korzyści w zakresie wydajności. Użytkownicy chętnie zaakceptują ograniczenia, jeśli będą mieli jasny obraz potencjalnych korzyści. Dlatego warto wyjaśnić ten kompromis w dokumentacji i innych opublikowanych materiałach dotyczących komponentu.

Możesz jednak znaleźć obejście tego problemu bez obniżania wydajności. Podczas tworzenia komponentu obrazu Next.js otrzymywaliśmy na przykład skargi, że trudno jest sprawdzić rozmiary lokalnie przechowywanych obrazów. Dodaliśmy importowanie statycznych obrazów, które usprawnia ten proces, automatycznie pobierając wymiary lokalnych obrazów w momencie kompilacji za pomocą wtyczki Babel.

Znajdź równowagę między wygodą a optymalizacją wydajności

Jeśli komponent z obrazem nie robi nic oprócz narzucania użytkownikom „użytecznego oporu”, deweloperzy prawdopodobnie nie będą chcieli z niego korzystać. Okazało się, że najważniejsze są funkcje związane z wydajnością, takie jak dostosowywanie rozmiaru obrazu i automatyczne generowanie wartości srcset. Zainteresowanie komponentem Image w ramach Next.js zwiększyły też udogodnienia dla deweloperów, takie jak automatyczne ładowanie leniwie i wbudowane nieostre substytuty.

Określ plan rozwoju funkcji, aby zwiększyć ich stosowanie

Stworzenie rozwiązania, które działałoby idealnie we wszystkich sytuacjach, jest bardzo trudne. Możesz mieć pokusę, aby zaprojektować coś, co dobrze działa u 75% użytkowników, a potem powiedzieć pozostałym 25% użytkowników, że „w tych przypadkach ten komponent nie jest dla nich”.

W praktyce ta strategia jest sprzeczna z Twoimi celami jako projektanta komponentów. Chcesz, aby deweloperzy wykorzystywali Twój komponent, aby korzystać z jego zalet. Jest to trudne, jeśli część użytkowników nie może przejść na nową wersję i czuje się pominięta. Potencjalnie wyrażają rozczarowanie, co prowadzi do negatywnego postrzegania, które wpływa na rozpowszechnienie.

Zalecamy, aby w przypadku komponentu mieć plan rozwoju, który obejmuje wszystkie rozsądne przypadki użycia w długim okresie. Warto też jasno określić w dokumentacji, co nie jest obsługiwane i dlaczego, by wyjaśnić, jakie problemy dany komponent ma rozwiązać.

Podsumowanie

Korzystanie z obrazów i ich optymalizacja jest skomplikowane. Deweloperzy muszą znaleźć równowagę między wydajnością a jakością obrazów, dbając jednocześnie o wygodę użytkowników. To sprawia, że optymalizacja obrazów jest przedsięwzięciem o dużych kosztach i dużej sile oddziaływania.

Nie wymagamy, aby każda aplikacja za każdym razem odmieniała nowe rozwiązanie. Opracowaliśmy szablon sprawdzonych metod, który deweloperzy, platformy i inne platformy technologiczne mogliby wykorzystać jako punkt odniesienia we własnych implementacjach. To doświadczenie okaże się bardzo przydatne, ponieważ obsługujemy inne frameworki w ich komponentach obrazkowych.

Komponent Image w Next.js poprawił wydajność aplikacji Next.js, co zwiększyło wygodę użytkowników. Uważamy, że jest to świetny model, który sprawdzi się w szerszym ekosystemie. Chcielibyśmy dowiedzieć się więcej od deweloperów, którzy chcieliby zastosować ten model w swoich projektach.