
Ten poradnik prowadzi przez budowę działających zakładek w HTML, CSS i JavaScripcie. Pomysł jest prosty: dokładasz do strony pliki CSS i JavaScript, nadajesz zagnieżdżonym divom kilka klas i całość działa. Spróbujemy przy okazji zachować poprawną semantykę, dokładając atrybuty ARIA do właściwych elementów. Nie traktuj jednak tej części jako wzorca, bo nie mam pewności, czy trafiłem we wszystko idealnie. Tak czy inaczej, nadanie znaczenia strukturze HTML zawsze się opłaca.
See the Pen
Onglets HTLM/CSS/JS by Damien Flandrin (@dam62500)
on CodePen.0
Struktura HTML zakładek
Tak może wyglądać znacznikowa struktura zakładek. Najpierw div z klasą tabs, a w nim dwa divy: jeden z klasą tab-registers, drugi z klasą tab-bodies. Pierwszy przycisk wewnątrz tab-register jest powiązany z pierwszym elementem w tab-bodies. Jednemu z przycisków możemy nadać klasę active-tab, żeby stał się zakładką aktywną.
Zakładki HTML
<div class="tabs">
<div class="tab-registers">
<button class="active-tab">Tab 1</button>
<button>Tab 2</button>
<button>Tab 2</button>
</div>
<div class="tab-bodies">
<div style="display:block;">
Contenu Tab 1
</div>
<div style="display:none;">
Contenu Tab 2
</div>
<div style="display:none;">
Contenu Tab 3
</div>
</div>
</div>JavaScript obsługujący zakładki
Zaczynamy od pobrania kolekcji HTML wszystkich elementów z klasą tabs i przechodzimy po niej pętlą, wcześniej zamieniając ją na listę przez Array.from(list_like). Callback pętli dostaje sam element oraz jego indeks. Indeks jest nam potrzebny, żeby jednoznacznie zidentyfikować każdą grupę zakładek.
Zakładki JavaScript
Array.from(document.querySelectorAll('.tabs')).forEach((tab_container, TabID) => {
...
})Następnie zapisujemy w zmiennych te elementy, które nas interesują, czyli tabs-registers i tab-bodies.
Zakładki JavaScript
const registers = tab_container.querySelector('.tab-registers');
const bodies = tab_container.querySelector('.tab-bodies');Idziemy dalej i pobieramy aktywny rejestr. Może być niezdefiniowany, wtedy przyjmujemy pierwszy przycisk. W obu przypadkach dodajemy elementowi klasę active-tab.
Zakładki JavaScript
let activeRegister = registers.querySelector('.active-tab');
activeRegister = activeRegister ? activeRegister : registers.children[0]
activeRegister.classList.add('active-tab')Potem wołamy funkcję changeBody(), która robi dokładnie to, co mówi jej nazwa: ukrywa wszystkie panele poza tym, który odpowiada bieżącemu rejestrowi, czyli bieżącej zakładce. Tę samą funkcję wywołujemy przy każdej zmianie zakładki.
Teraz przechodzimy pętlą po wszystkich elementach leżących bezpośrednio w kontenerze rejestrów, czyli po przyciskach, po samych zakładkach. Każdemu ustawiamy atrybut aria-controls, który wskazuje kontrolowany element, i nadajemy odpowiadającemu panelowi pasujący id. Ten id budujemy z id grupy zakładek i indeksu przycisku, więc jest unikalny.
Potem podpinamy do przycisku nasłuchiwanie zdarzenia: zdejmujemy klasę aktywnej zakładki z bieżącego rejestru, podmieniamy zawartość zmiennej na nowy bieżący rejestr i jemu z kolei nadajemy klasę aktywnej zakładki. Na koniec wołamy funkcję changeBody().
Zakładki JavaScript
Array.from(registers.children).forEach((el, i) => {
el.setAttribute('aria-controls', `${TabID}_${i}`)
bodies.children[i]?.setAttribute('id', `${TabID}_${i}`)
el.addEventListener('click', (ev) => {
let activeRegister = registers.querySelector('.active-tab');
activeRegister.classList.remove('active-tab')
activeRegister = el;
activeRegister.classList.add('active-tab')
changeBody(registers, bodies, activeRegister)
})
})Teraz sama funkcja changeBody(). W środku ponownie przechodzimy pętlą po rejestrach potomnych i bierzemy indeks każdej iteracji. Jeśli panel o tym indeksie istnieje, ustawiamy jego właściwość display na block albo none, zależnie od bieżącego rejestru. Ten warunek jest po to, że rejestrów może być więcej niż paneli. Ustawiamy też przyciskowi atrybut aria-expanded, który mówi użytkownikowi, czy panel, albo przycisk prowadzący do panelu, jest rozwinięty.
Zakładki JavaScript
function changeBody() {
Array.from(registers.children).forEach((el, i) => {
if (bodies.children[i]) {
bodies.children[i].style.display = el == activeRegister ? 'block' : 'none'
}
el.setAttribute('aria-expanded', el == activeRegister ? 'true' : 'false')
})
}CSS zakładek
Zakładki CSS
.tabs {
font-family: 'Lucida Sans', sans-serif;
font-size: 20px;
}
.tabs .tab-registers {
display: flex;
background-color: RGB(255, 255, 255);
}
.tabs button {
padding: 0.5em;
background-color: RGB(255, 255, 255);
border: none;
font: inherit;
}
.tabs .tab-registers button:hover {
cursor: pointer;
}
.tabs .tab-bodies {
padding: 0.5em;
background-color: RGB(235, 235, 235);
flex-grow: 1;
overflow-y: auto;
}
.tabs button.active-tab {
background-color: rgb(235, 235, 235);
}

