Przyspieszamy ASP.NET - CSS Sprites
Zwykle na bogaty interfejs graficzny witryny składa się wiele obrazków. Nawet, gdy każdy z nich zajmuje po klika kilobajtów, to czas ich pobierania może zacząć dominować przy ładowaniu strony. W końcu załadowanie każdej grafiki wymaga nawiązania oddzielnego połączenia z serwerem. A gdyby tak dało się przesłać wszystkie obrazki w ramach jednego żądania do serwera… Da się i technika ta nazywa się CSS Sprites!
Koncepcja, którą przedstawię w tym wpisie, nie jest w żaden sposób powiązana z ASP.NET – można ją stosować na dowolnej stronie HTML, niezależnie, jaka technologia znajduje się na serwerze. Stanowi ona jeden z ważniejszych sposobów optymalizacji witryn WWW, dlatego uważam, iż nie można o niej zapomnieć, pracując nad wydajnością aplikacji ASP.NET.
W czym problem?
Przeanalizujmy pewien przykład:
http://demos.telerik.com/ribbonbardemo/Examples/Client/ClientMockup.aspx
Na powyższej stronie znajduje się bardzo ciekawy przykład aplikacji ASP.NET, która wykorzystuje interfejs graficzny z Office 2007 i została zrealizowana przez jednego z użytkowników kontrolek Telerika. Z tą stroną jest jednak pewien problem – zobaczmy, jak wygląda jej pierwsze załadowanie:
| | Ilość żądań | Czas [s] | Rozmiar [KB] |
| Obrazki | 112 (81%) | 3,96 (77%) | 122 (29%) |
Pozostałe (HTML, JS, CSS) | 26 (19%) | 1,21 (23%) | 298 (71%) |
Do jej załadowania przeglądarka musi wysłać aż 138 żądań do serwera, z czego aż 112 przypada na obrazki. Paradoksalnie, choć rozmiar wszystkich obrazków stanowi jedynie 29% całego transferu, to na ich pobranie poświęcanych jest aż 77% czasu.
“Duszki” CSS
Technika CSS Sprites polega na zastąpieniu wielu małych obrazków jednym dużym. Takie rozwiązanie ma kilka zalet:
- Redukcja ilości żądań do serwera – jeden obrazek to jedno połączenie.
- Mniejszy transfer - jeden zbiorczy obrazek zajmuje mniej przestrzeni niż wiele mniejszych.
- Układ graficzny strony pojawia się od razu, a nie ładuje w kawałkach.
- Brak efektu “doładowywania” się grafik od dynamicznych elementów interfejsu graficznego (np. krawędzi menu pojawiającego się po kliknięciu).
Jak to wygląda w rzeczywistości? Zobaczmy sprite z biblioteki jQuery UI:
| Jeden obrazek Żądania do serwera: 1 Rozmiar: 8 KB Oddzielne obrazki Żądania do serwera: 173 Rozmiar: 28 KB |
Teraz przejdźmy do meritum – jak sprawić, żeby zamiast dużego obrazka pokazał się jego wycinek? Zabawa sprowadza się do manipulacji 5 ustawieniami CSS:
- width – szerokość elementu (obrazka)
- height – wysokość elementu (obrazka)
- background-image – obrazek tła
- background-position – przesunięcie obrazka tła
- background-repeat – powtarzanie obrazka tła
Przypadek 1: Sprite w elemencie blokowym
Jeżeli chcemy wstawić sprite w elemencie blokowym (np. <div>, <p> lub inny z ustawionym stylem “display: block”), to sprawa jest prosta. Przykładowo, żeby wyświetlić kopertę z powyższego obrazka (7 rząd, 6 kolumna) potrzebny jest następujący kod CSS i HTML:
1: <style type="text/css">
2: .icon-mail
3: {
4: width: 16px; /*szerokość sprite'a*/
5: height: 16px; /*wysokość sprite'a*/
6: background-image: url(sprite.png); /*obrazek zbiorczy*/
7: background-position: -80px -96px; /* -(6-1)*16px -(7-1)*16px */
8: background-repeat: no-repeat;
9: }
10: </style>
11: ...
12: <div class="icon-mail"></div>
Krótkie słowo wyjaśnienia, skąd wzięły się wartości w background-position:
- -80px – gdyż obrazek zbiorczy trzeba przesunąć o 5*16px w lewo, aby ikonka koperty znalazła się tuż przy jego lewej krawędzi.
- -96px – gdyż obrazek zbiorczy trzeba przesunąć o 6*16px do góry, aby ikonka koperty znalazła się tuż przy jego górnej krawiędzi.
Oczywiście, znacznie wygodniej jest utworzyć sobie kilka klas obsługujących cały obrazek, np.
1: <style type="text/css">
2: .icon16x16
3: {
4: width: 16px;
5: height: 16px;
6: background-image: url(sprite.png);
7: background-repeat:no-repeat;
8: }
9: .sprite16x16-1-1 { background-position: 0px 0px; }
10: .sprite16x16-1-2 { background-position: -16px 0px; }
11: .sprite16x16-1-3 { background-position: -32px 0px; }
12: .sprite16x16-2-1 { background-position: 0px -16px; }
13: .sprite16x16-2-2 { background-position: -16px -16px; }
14: .sprite16x16-2-3 { background-position: -32px -16px; }
15: </style>
16: ...
17: <div class="icon16x16 sprite16x16-2-2"></div>
Przypadek 2: Sprite w elemencie “inline”
Często potrzebujemy, aby sprite zachowywał się jak zwykły obrazek w tagu <img>, który można umieszczać np. w tekście. Innymi słowy, żeby zachowywał się jak element typu “inline”. Problem polega na tym, że takim elementom nie można ustawiać wysokości ani szerokości, więc gdybyśmy wstawili sprite w element <span>, to nie wyświetliłby się poprawnie.
Jednym z rozwiązań może być ustawienie “display: inline-block” (CSS 2.1). Przykładowy kod wygląda następująco:
1: <style type="text/css">
2: .icon16x16
3: {
4: display: inline-block;
5: width: 16px;
6: height: 16px;
7: background-image: url(sprite.png);
8: background-repeat:no-repeat;
9: }
10: .sprite16x16-1-1 { background-position: 0px 0px; }
11: .sprite16x16-1-2 { background-position: -16px 0px; }
12: .sprite16x16-2-1 { background-position: 0px -16px; }
13: .sprite16x16-2-2 { background-position: -16px -16px; }
14: </style>
15: ...
16: Oto ikonka <span class="icon16x16 sprite16x16-2-2"></span> w tekście.
Powyższe rozwiązanie ma pewne wady związane z niepełną/niepoprawną interpretacją “inline-block” przez starsze przeglądarki. Nie zadziała to w FireFoxie 2.0 i starszych, natomiast w IE 6 i 7 element, któremu ustawiamy “display: inline-block” musi mieć oryginalnie “display: inline” (dlatego <span> dobrze nadaje się do tego celu).
Przypadek 3: Sprite w elemencie o zmiennej szerokości lub wysokości
Technika CSS Sprites może być również zastosowana do obrazków, które mają zmienną szerokość lub wysokość. Przypuśćmy, iż chcemy zrobić ładny pasek postępu w trzech kolorach: zielonym, żółtym i czerwonym. Oczywiście, każdy pasek będzie miał szerokość proporcjonalną do prezentowanego postępu. Wówczas tworzymy obrazek, w którym pionowo jeden pod drugim umieszczamy wypełnienia dla pasków:
Teraz możemy użyć następującego kodu:
1: <style type="text/css">
2: .progressbar
3: {
4: width: 100px;
5: height: 10px;
6: border: solid 1px;
7: margin: 2px;
8: }
9: .progressbar>div
10: {
11: height: 10px;
12: background-image: url(ProgressBarFill.png);
13: background-repeat: repeat-x;
14: }
15: .progressbar-red { background-position: 0px 0px; }
16: .progressbar-yellow { background-position: 0px -10px; }
17: .progressbar-green { background-position: 0px -20px; }
18: </style>
19: ...
20: <div class="progressbar"><div style="width: 20px" class="progressbar-red"></div></div>
21: <div class="progressbar"><div style="width: 50px" class="progressbar-yellow"></div></div>
22: <div class="progressbar"><div style="width: 90px" class="progressbar-green"></div></div>
Efekt będzie następujący:

W przypadku elementów o zmiennej wysokości postępujemy analogicznie.
Przecież to tyle pracy…
Jeżeli uważasz, że przygotowanie obrazków, klas CSS oraz przebudowanie stron, aby używały CSS Sprites, to dużo pracy… masz rację. Ale zawsze są narzędzia, które nam tą pracę ułatwiają (na przykład tutaj możemy połączyć obrazki i wygenerować do nich klasy CSS). Poza tym, jest to wysiłek jednorazowy.
Podsumowanie
Mam nadzieję, że tym wpisem przekonam kogoś do stosowania CSS Sprites. Oczywiście, gdy interfejs naszej witryny składa się z trzech obrazków na krzyż, to gra nie jest warta świeczki. Natomiast, przy większej ich ilości wydaje mi się, iż efekt końcowy rekompensuje pracę, którą trzeba wykonać na początku.