Nowy sposób prezentacji postępu i użycia – Progress Ring

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.

Download · Live Demo

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.