Podczas luźnych dyskusji o interfejsie użytkownika w firmowym panelu administracyjnym zapytano mnie.
Czy możliwe jest wycięcie danego fragmentu koła przy pomocy CSS?
Oczywiście jako członek zespołu programistycznego od razu rzuciłem w odpowiedzi żelazną zasadę naszego zespołu, czyli Nie da się!. Szybko jednak okazało się, że to możliwe, a narysowanie wykresu kołowego w CSS, wcale nie jest tak trudne jak myślałem w pierwszej chwili. Wszystko to kwestia odpowiedniego podejścia i użycia dostępnych środków, czyli właściwości clip
z CSS 2.1, a także border-radius
oraz transform
z CSS3.
Omawiany wycinek koła był potrzebny do oznaczenia aktualnego użycia lub postępu, co ostatecznie sprowadziło się do prezentacji jednej wartości względem całości koła lub pierścienia jako 100% naszych zasobów. Podczas działań sportowych na rzecz jak najmniejszej ilości kodu, a także bardzo uniwersalnego rozwiązania planowałem wykorzystanie pseudo-elementów ::before
i ::after
, choć ostatecznie pozostałem przy warstwach. Pomijam wsparcie tej części CSS w niektórych przeglądarkach, ale prezentowany przykład jest bardziej eksperymentem niż docelowym rozwiązaniem do gotowego i szerokiego stosowania.
Struktura kodu dla naszych przykładów jest bardzo prosta.
<div class="progress-usage">
</div>
<div class="progress-indicator less">
</div>
<strong>20</strong><span>%</span>
</div>
Zaczynamy od stworzenia elementudiv
o klasie .progress-ring
, który będzie bazą dla każdego pierścienia. Wewnątrz tworzone jeszcze dwa elementy div
do oznaczenia aktualnego stanu poprzez wybrany kolor, a mianowicie .progress-usage
oraz .progress-indicator
.
.progress-ring,
.progress-usage,
.progress-indicator {
width: 150px;
height: 150px;
display: block;
border: 25px solid #eee;
border-radius: 100px;
}
Wszystkie trzy są takimi samymi elementami, ale ze względu na użycie obramowania, konieczne jest ich wzajemne pozycjonowanie, a pokrywały się.
.progress-ring {
float: left;
margin: 0 25px;
text-align: center;
font: normal 100% sans-serif;
position: relative;
border-color: #eee;
}
.progress-usage,
.progress-indicator {
position: absolute;
top: -25px;
left: -25px;
}
.progress-usage {
border-color: #0a0;
clip: rect(0px, 200px, 200px, 100px);
}
Jeśli zechcesz w ramach eksperymentów lub dalszej optymalizacji możliwe jest zastąpienie ostatnich dwóch przez pseudo-elementy :before
i :after
. Prezentowanie wartości liczbowej zostało przekazane do dodatkowych elementów strong
oraz span
.
.progress-ring strong {
font-size: 4em;
color: #444;
line-height: 150px;
}
.progress-ring > span {
display: block;
width: 150px;
margin: 0 auto;
font-size: 2em;
font-weight: bold;
color: #aaa;
position: absolute;
top: 100px;
}
Elementy te akurat mają najmniejsze znaczenie do zrozumienia zasady działania całego mechanizmu.
Zasada działania
Główny element .progress-ring
stanowi pierścień o szarym tle, który w zależności od wartości wypełnia się danym kolorem. Wypełnienie pewnej części kolorem jest czynnością dwuetapową. Mianowicie podstawą wszystkiego jest odpowiednie wycięcie z określonego obszaru połowy jego zawartości, i przykrycie go kolejnym elementem, dodatkowo obróconym.
Zostały stworzone dwie dodatkowe klasy .less
i .more
, które przeznaczone są do generowania połowy pierścienia w odpowiednim kształcie i kolorze.
.less {
clip: rect(0, 200px, 200px, 100px);
border-color: #eee;
}
.more {
clip: rect(0, 100px, 200px, 0);
border-color: #0a0;
}
Zakres 0-50%
Ponieważ z warstwy .progress-usage
wycinamy tylko prawy obszar to od razu otrzymujemy zaznaczenie wielkości 50%. Jest to doskonale widoczne poprzez minimalny obrys pierścienia dla wartości mniejszych niż połowa. Element .progress-indicator
, z dodatkową klasą .less
jest odpowiednikiem .progress-usage
, ale w kolorze szarym. Uzyskanie każdej wartości z zakresu 0-50% wiąże się z przykrywaniem pierścienia przez ten element i dadatkowym obrotem zgodnie z wskazówkami zegara. Wartość obrotu łatwo obliczymy przy założeniu, że jeden procent to 3.6 stopnia.
Przykładowo dla 20% wartość obrotu wynosi transform: rotate(72deg);
.
Zakres 50-100%
Zaznaczenia powyżej 50% generowane są podobnie, ale element .progress-indicator
ma wówczas klasę .more
, która wycina sąsiednią połowę obszaru, a także zmienia kolor obramowania na aktywny, w naszym przekładnie zielony. Dalsze wypełnianie pierścienia bazowego jest analogiczne i wiąże się z obrotem elementu .progress-indicator
od 180 do 360 stopni.
Z kolei dla 60% zaznaczenia wartość obrotu wynosi odpowiednia transform: rotate(216deg);
.
Zaskakujące jest, że Google Chrome (przynajmniej w wersji do 25) nie obsługuje prawidłowo obrotu elementu, choć przy użyciu jQuery w wersji animowanej wszystko działa jak powinno. Prosty sposób naprawy tego stanu jest użycie określenie właściwości z prefiksem przeglądarki, czyli -webkit-transform.
Podsumowanie
Po raz kolejny przekonałem się, że nie ma rzeczy niemożliwych, a jedynie potrzebujesz odrobiny czasy i zaangażowania do znalezienia odpowiedniego rozwiązania. Idąc dalej, tym tropem dochodzę do wniosku, że HTML5 oraz CSS3 będą w najbliższych latach stanowiły prawdziwe narzędzia, umożliwiające uzyskanie efektów zarezerwowanych wcześniej choćby dla Flash czy SVG.