Większość aplikacji webowych wciąż traktuje przesyłanie obrazów jak czarną skrzynkę. Użytkownik wrzuca plik, przeglądarka go wysyła, a serwer albo przyjmuje payload, albo wyrzuca błąd 413, na który nikt nie był przygotowany. Kompresja po stronie przeglądarki zmienia postać rzeczy. Daje szansę na zmniejszenie payloadu, zanim trafi on do sieci, co oznacza szybsze przesyłanie, niższe rachunki za transfer i mniej przekroczeń czasu oczekiwania na serwerze. Ale łatwo to zepsuć. Jeśli potraktujesz kompresję jak magiczny suwak z etykietą „jakość”, będziesz wysyłać uszkodzone zdjęcia, rozciągnięte miniatury i budować frustrujące doświadczenia użytkownika. Lepszym podejściem jest traktowanie całego procesu jako pipeline'u.

Myśl w kategoriach pipeline'ów, a nie suwaków

Podziel zadanie na odrębne etapy. Odczytaj plik z elementu input. Zmniejsz obraz do docelowych wymiarów. Zakoduj nowy obiekt Blob. Następnie wyrenderuj wynik z powrotem dla użytkownika. Każdy etap robi jedną rzecz i przekazuje swój wynik do następnego. Ta separacja to nie tylko czystszy kod. To sprawia, że testy jednostkowe stają się proste. Możesz podać znany bufor do etapu skalowania bez dotykania elementu input pliku. Możesz zweryfikować, czy Twój enkoder generuje plik JPEG poniżej 200 KB bez czekania na odpowiedź z serwera. Gdy coś się zepsuje, dokładnie wiesz, który krok zawiódł.

Rozdzielenie tych obowiązków zapobiega również niespodziankom podczas przesyłania. Jeśli połączysz skalowanie i kodowanie w jedną splątaną funkcję, błąd dekodowania w połowie procesu może pozostawić kolejkę przesyłania w niespójnym stanie. Pipeline wymusza walidację na każdym styku. Jeśli pliku nie można zdekodować, wyłapiesz to, zanim w ogóle utworzysz canvas. Jeśli zakodowany Blob jest zbyt duży, wyłapiesz to, zanim poprosisz serwer o jego zapisanie.

Zdefiniuj kontrakt, zanim zaczniesz pisać kod

Zanim ktokolwiek napisze wywołanie rysowania na canvasie, spisz zasady i podziel się nimi z zespołem. Wybierz akceptowane typy MIME. Czy będziesz pozwalać na JPEG, PNG, WebP czy AVIF? Każdy z nich ma inne implikacje dla kanałów alfa, wsparcia przeglądarek i obciążenia procesora. Ustal maksymalny rozmiar wejściowy. Surowe zdjęcie o rozmiarze 30 MB z flagowego telefonu może zamrozić lub zawiesić stary laptop, jeśli spróbujesz je w całości zdekodować w pamięci. Zdefiniuj maksymalne wymiary wyjściowe. Jeśli Twój interfejs nigdy nie wyświetla obrazów szerszych niż 2048 pikseli, nie ma powodu, by przepuszczać przez pipeline zdjęcie o szerokości 6000 pikseli.

Co najważniejsze, zaplanuj błędy dekodowania. Uszkodzony plik, egzotyczny profil kolorów lub przerwane przesyłanie mogą spowodować błąd w konstruktorze Image. Twój pipeline potrzebuje wyraźnego bloku catch i czytelnego dla człowieka komunikatu o błędzie. Nie pozwól, aby przeglądarka po cichu przestała działać, zostawiając użytkownika wpatrzonego w kręcący się spinner, podczas gdy nic się nie dzieje.

Szanuj obraz

Zniekształcenia wyglądają amatorsko. Zachowaj proporcje i ogranicz najdłuższy bok. Jeśli Twój docelowy obszar to 1024 na 1024 piksele, zdjęcie o wymiarach 4000 na 3000 powinno zostać przeskalowane do 1024 na 768, a nie do 1024 na 1024. Oblicz współczynnik skali na podstawie dłuższego boku i pozwól krótszemu boku podążyć za nim. Zapobiega to rozciąganiu obrazów do dziwnych kształtów.

Do właściwego eksportu użyj metody toBlob obiektu canvas. Daje ona bezpośrednią kontrolę nad formatem wyjściowym i ustawieniem jakości, a ponieważ działa asynchronicznie, nie blokuje głównego wątku. Utwórz offscreen canvas, narysuj na nim przeskalowany obraz, a następnie wywołaj canvas.toBlob z preferowanym typem i wartością jakości. Ten nowy Blob to właśnie to, co przekazujesz do swojej logiki przesyłania lub API do przechowywania danych.

Pokaż dowody

Kompresja to niewidoczna praca. Jeśli nie pokażesz liczb, użytkownicy nie zaufają procesowi. Zbuduj interfejs, który pozwoli im porównać oryginał z wynikiem. Wyświetl oryginalny rozmiar pliku, nowy rozmiar pliku, nowe wymiary i końcowy typ formatu. Zobaczenie, jak zdjęcie z telefonu o rozmiarze 4,2 MB zmienia się w plik WebP o rozmiarze 380 KB, usuwa obawę, że po kryjomu niszczysz ich zdjęcie.

Ta przejrzystość pomaga również w rozwiązywaniu problemów. Gdy użytkownik skarży się, że przesyłanie się nie powiodło, pierwszą rzeczą, którą sprawdzisz, będzie to, czy wymiary wyjściowe nie przekroczyły limitu serwera lub czy format nie zmienił się z PNG na JPEG, tracąc kanał alfa. Umieść te dane w interfejsie, aby użytkownik mógł samodzielnie zdiagnozować problem, zanim otworzy zgłoszenie do wsparcia.

Presety wygrywają z ponowną kompresją

Nigdy nie kompresuj tego samego obrazu dwa razy. Każde przejście przez enkoder stratny usuwa więcej szczegółów i wprowadza artefakty blokowe. Jeśli pozwolisz użytkownikowi wielokrotnie klikać „optymalizuj”, trzecia generacja będzie wyglądać jak kserokopia kserokopii. Zamiast tego generuj każdy wynik z oryginalnego pliku źródłowego i oferuj presety:

  • Mniejszy plik: Obniż jakość i agresywnie ograniczaj wymiary dla miniatur lub szybkich podglądów.
  • Zrównoważony: Celuj w umiarkowany poziom jakości z rozsądnymi wymiarami, odpowiedni dla kanałów społecznościowych i galerii.
  • Więcej szczegółów: Zachowaj wysoką jakość i większe wymiary dla fotografii, dzieł sztuki lub podglądów do druku.

Przechowuj oryginalny obiekt Blob w pamięci, aby użytkownik mógł przełączać się między ustawieniami bez kumulowania strat wynikających z wielokrotnej kompresji. Zawsze generuj z pliku źródłowego, nigdy z ostatniego wyniku.

Testuj tak, jak użytkownicy przesyłają pliki

Twoja maszyna deweloperska z łączem światłowodowym i 32 GB pamięci RAM nie jest rzeczywistością. Testuj na rzeczywistych plikach, które noszą prawdziwi użytkownicy. Zdjęcia z telefonów iOS i Android używają różnych orientacji metadanych i mogą pochodzić z plików HEIC. Przezroczyste zasoby, takie jak logotypy i ikony, zachowują się inaczej podczas konwersji do JPEG, ponieważ format JPEG po prostu nie obsługuje kanałów alfa. Ogromne pliki obnażą limity pamięci na urządzeniach z 2 GB RAM. Wolne procesory mobilne pokażą dokładnie, jak długo w rzeczywistości trwa wywołanie toBlob.

Użyj Chrome DevTools, aby ograniczyć wydajność procesora i sieci. Wypróbuj pięcioletni telefon z Androidem. Jeśli Twój potok przetwarzania blokuje interfejs użytkownika na trzy sekundy podczas kodowania, musisz przenieść ciężkie zadania do Web Workera, aby interfejs pozostał responsywny.

Najpierw dostarcz podstawy

Kuszące jest wspieranie każdego formatu i