Jun 29, 2009
Zoptymalizuj duszka – CSS Sprites zoptymalizowane
W życiu każdego popularnego serwisu internetowego przychodzi czas, kiedy należy zabrać się za jego optymalizację. Zwykle dokupuje się dodatkowe serwery, wpina memcache, optymalizuje kod serwisu i zapytania do bazy danych. Frontend jest zwykle pomijany, choć w rzeczywistości, optymalizując szablony, można uzyskać całkiem niezłe wyniki relatywnie niskim nakładem sił i środków.
Ponieważ miałem ostatnio kilka serwisów, które trzeba było zoptymalizować, postanowiłem przypomnieć sobie technikę CSS Sprites opisaną przez Dave’a Shea na łamach AListApart. Jeśli ktoś jej nie zna – proponuję się zapoznać. To prosta technika, z korzeniami w świecie gier komputerowych, polegająca na łączeniu wielu plików w graficznych w jeden większy, łatwiejszy w zarządzaniu i zmniejszający ilość zapytań o elementy graficzne.
Niedawno przetestowałem to rozwiązanie w kilku serwisach, które aż prosiły się o optymalizację i trafiłem na ciekawe zachowanie. Ale po kolei. Zakładając, że nasz przeciętny Jan Kowalski, czyta artykuł na ALA po raz pierwszy, skorzysta z zapisu zaproponowanego w artykule:
#panel1b a:hover {
background: transparent url(test-3.jpg)
0 -200px no-repeat;}
#panel2b a:hover {
background: transparent url(test-3.jpg)
-96px -200px no-repeat;}
#panel3b a:hover {
background: transparent url(test-3.jpg)
-172px -200px no-repeat;}
#panel4b a:hover {
background: transparent url(test-3.jpg)
-283px -200px no-repeat;}
Puryści i fanatycy optymalizacji już w tym momencie powinni (już możecie) złapać się za głowę. Ale to nie nieoptymalny zapis jest problemem; prawdziwy problem rodzi się, kiedy chcemy skorzystać z tego rozwiązania w serwisie, w którym nasz plik z “duszkami” ma ponad 350kb, przeciętny obiekt wycinany z pliku ma wymiary rzędu 250×160px a takich wycinanek mamy na jednej podstronie około dwudziestu.
W przeciwieństwie do demonstracyjnego pliczku (parę kb) załadowanie takiego potworka chwilę zajmuje i – jak się okazuje – wpływa na sposób budowania strony… w Firefoxie.
Chcieliście napisać: no jasne, że w Internet Explorerze? Właśnie nie. Ten – o dziwo – renderuje stronę tak, jak byśmy chcieli: po załadowaniu pliku, pojawiają się wszystkie elementy graficzne. Od razu. Ale nie w Firefoxie; Firefox wycina je po kawałku tak, jak by nic się nie zmieniło.
Ok, co jest źle z tym zapisem? Przede wszystkim: zupełnie zbędnie powtarza się ten sam kawałek kodu tylko po to, by zmienić jedną/dwie wartości. Deklarację tła i jego powtarzanie możemy przenieść w jedno miejsce tak, by później tylko deklarować co chcemy wyciąć z tego pliku. Poprawniejszy zapis, może wyglądać tak:
#panel1b, #panel2b, #panel3b, #panel4b {
background: url(test-3.jpg);
background-repeat: no-repeat;
}
panel1b {
background-position: 0 -200px;
}
…
I to jest rozwiązanie warte kilka sekund renderowania strony. Firefox nie lubi się z powtarzaniem całej deklaracji ładowanego tła – za każdym razem otwiera plik z grafiką, wycina i wkleja potrzebny fragment wydłużając cały proces w swoistą “nieskończoność”. W moim przypadku, zmieniając deklarację udało mi się zejść z 16-20s renderowania strony (wg YSlow) do 6.
Powiązane wpisy:
firefox nie lubi powtorzonej deklaracji tla z wylaczonym cachem, pobiera wtedy sprita wiele razy.
Ten wynik byl mierzony z cachem czy bez ?
Warto jeszcze pamiętać, aby nie przesadzić z ilością sprite’ów w pliku – jeśli są to ikony bądź gradienty, to nie ma problemu, natomiast jeśli ładujemy w niego wszystkie wielkie grafiki (dla divów z zaokrąglonymi rogami np), to finalny plik może przekroczyć parę tysięcy pikseli.
Ktoś pewnie zapyta, jaki w tym problem, przecież whitespace jest za darmo, kompresja w PNG działa, a plik zajmuje raptem 20KB. Tak, ale na dysku. Ten plik jest wyświetlany jako bitmapa, a ta może sięgnąć nawet kilkudziesięciu MB (w przypadku takich ogromnych przestrzeni). Zaoszczędzimy więc na wadze i HTTP requests, natomiast obciążymy przeglądarkę usera dodatkowymi megabajtami i zeżremy solidnie CPU. I nie są to czcze słowa, ktoś to przetestował.
Dlatego – stosujmy sprite’y rozsądnie i tam gdzie warto.
A o tym będzie w kolejnej części bajki. Gdzieś w okolicach gwiazdki.
Przyszłego roku.
@klucznik FF nie pobiera sprajta wiele razy z sieci, on go za każdym razem otwiera i zamyka w pamięci. Pewnie ktoś z ekipy mozilli mógłby fachowo określić co się dzieje. Efekt jest taki, że każdy element w którym pojawia się deklaracja tego samego pliku jest odrysowywany niezależnie
dodam tylko, że zoptymalizowałbym własność jeszcze bardziej – i zamiast:
background: url(test-3.jpg);
zapisałbym:
background-image: url(test-3.jpg);
(Teraz następuje mierzenie wirtualnych końcówek)
background: url(test-3.jpg) no-repeat; wydaje się być najoptymalniejsze. background-position niżej nadpisze domyślne ‘0 0′ pominięte we wspólnej deklaracji.
W zasadzie tak, macie rację (wyszedłem już trochę z wprawy). Najważniejsze by w FF przyspieszyć.