Zaznaczanie aktywnych elementów HTML bez użycia JavaScript

Treść dodana: 08 września 2017.

Często spotykaną sytuacją jest potrzeba zaznaczenia aktywnego elementu po kliknięciu w niego. Nie namyślając się długo, młody adept sztuki web-devu wykorzysta w tym celu jQuery, popełniając jeszcze przy tym kilka dodatkowych błędów. Jako ilustrację omawianej sytuacji można podać grupę przycisków (sformatowanych frameworkiem Twitter Bootstrap):

<div class="btn-group" role="group" aria-label="...">
  <button type="button" class="btn btn-default">Left</button>
  <button type="button" class="btn btn-default">Middle</button>
  <button type="button" class="btn btn-default">Right</button>
</div>

Zazwyczaj unikam w tekście pokazywania złych nawyków, tym razem jednak sądzę że jest to uzasadnione. Nidgy nie używaj takiego kodu produkcyjnie.

$(function() {
    $("button").click(function() {
        $("button").removeClass("active");
        $(this).addClass("active");
    });
});

Kod po kliknięciu w `button` doda klasę `active` do aktywnego (klikniętego) przycisku. Działa? Owszem, ale… Nie wykorzystano delegacji zdarzeń dlatego zdarzenie zostało przypięte po kolei do każdego możliwego elementu `button` na stronie. Przy większej ich liczbie kod będzie wolniej się wykonywał. Gdyby do grupy dodać dynamicznie kolejny przycisk, kod nie będzie dla niego już działał – z tego samego powodu co wcześniej. Następuje niepotrzebna modyfikacja drzewa DOM poprzez zdejmowanie i dodawanie klas. Do działania niezbędna jest biblioteka jQuery – w zminifikowanej i skompresowanej wersji to co najmniej 35kB kodu. Co powinniśmy zrobić żeby podobny efekt uzyskać w samym HTML + CSS?

W pierwszej kolejności powinieneś zapoznać się Czytelniku z dokumentacją CSS dotyczącą selektorów. Oprócz bazowych selektorów które pewnie od dawna znasz, na liście znajduje się pseudo-klasa `:checked` którą można wykorzystać do uzyskania potrzebnego efektu. Ma ona zastosowanie do każdego zaznaczonego przycisku

<input type="radio"/>
<input type="checkbox"/>

oraz wybranego elementu `option` wewnątrz listy rozwijanej `select`. Dodatkowo możemy posłużyć się selektorem `E + F`, który wybiera element F bezpośrednio poprzedzony elementem E. Przepisany kod HTML:

<div class="btn-group btn-group-radio" role="group" aria-label="...">
    <input type="radio" name="group1" id="button1"/>
    <label for="button1" class="btn btn-default">Left</label>
    <input type="radio" name="group1" id="button2"/>
    <label for="button2" class="btn btn-default">Middle</label>
    <input type="radio" name="group1" id="button3"/>
    <label for="button3" class="btn btn-default">Right</label>
</div>

Dla rozróżnienia została dodana klasa `btn-group-radio`. Co będzie nam potrzebne od strony CSS? Przede wszystkim musimy ukryć wszystkie przyciski typu `radio`. Do zaznaczenia aktywnego przycisku wystarczy wybranie etykiety `label`. Należy także poprawić zaokrąglenie rogów pierwszego elementu `label`, dla którego style Bootstrap nie zadziałają. Następnie wykorzystując selektor `E + F` zmienimy tło wybranego przycisku na czerwone.

.btn-group-radio input[type="radio"] {
    display: none;
}

.btn-group-radio input[type="radio"]:checked + .btn {
    background-color: red;
}

.btn-group-radio > .btn {
    margin-left: -1px;
}

.btn-group-radio > input:first-child + .btn {
    border-top-left-radius: 4px !important;
    border-bottom-left-radius: 4px !important;
    margin-left: 0;
}

Działający kod można zobaczyć na stronie demonstracyjnej. Wystarczyło odrobinę przebudować HTML, dodać kilka linijek CSS i uzyskaliśmy podobny efekt – bez wykorzystania jQuery.

Zmiana stanu widoczności menu

Kolejnym bardzo częstym przypadkiem użycia JavaScript jest pokazywanie / ukrywanie menu. I po raz kolejny wystarczy odrobina kreatywności aby nie angażować skryptów do tego zadania. Przykładowy kod menu może mieć postać:

<!-- label może być umieszczony gdziekolwiek na stronie -->
<label for="expand-button" class="btn btn-primary">Pokaż / ukryj menu</label>

<input type="checkbox" id="expand-button" />

<nav id="menu">
    <ul>
        <li>
            <a href="#">Link1</a>
        </li>
        <li>
            <a href="#">Link2</a>
        </li>
        <li>
            <a href="#">Link3</a>
        </li>
    </ul>
</nav>

Tym razem przyda nam się selektor `E ~ F` – element F poprzedzony elementem E. Idea jest taka, aby `#menu` mogło być osadzone gdziekolwiek na stronie. Aby to działało, `input#expand-button` musi się zawsze znaleźć przed nim w kodzie. Wystarczy teraz kilka linijek CSS i gotowe:

label {
    cursor: pointer;
}

#expand-button {
    display: none;
}

#expand-button:checked ~ #menu,
#expand-button:checked ~ * #menu {
    display: block;
}

#menu {
    display: none;
}

Możemy chcieć też dodać animację pojawiającego się menu (czas 2 sekundy):

#expand-button:checked ~ #menu,
#expand-button:checked ~ * #menu {
    visibility: visible;
    position: static;
    opacity: 1;
    animation: fade 2s;
}

#menu {
    visibility: hidden;
    position: absolute;
    opacity: 0;
}

@keyframes fade {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

`@keyframes` podałem jako dodatkowy przykład. W zupełności wystarczyłaby sama konstrukcja:

transition: opacity 2s linear;

Demo skryptu.

Podsumowanie

Chociaż CSS pozwala na wiele ciekawych efektów, jest szybki i elastyczny w użyciu, to nie zawsze będziemy w stanie pominąć JavaScript. Podmianę tekstu w przyciskach łatwiej będzie wykonać w JS, do ustalenia stanu aktywnego przycisku potrzebny będzie jakiś `storage` (cookie, session/localStorage) który ponownie odczytamy w tym języku. Programowanie to dziedzina w której ustalenie złotego środka jest zawsze kluczem.

Komentarze

Nie ma jeszcze żadnych komentarzy do wyświetlenia. Może chcesz zostać pierwszą osobą która podzieli się swoją opinią?

Dodaj komentarz

*
Nazwa zostanie wyświetlona wraz z komentarzem. Możesz też utworzyć nowe konto w serwisie, dzięki czemu uzyskasz dodatkową funkcjonalność.
*
Akceptowana jest ograniczona składnia Textile. Wszystkie tagi HTML zostaną usunięte.