Перш ніж почнемо — демо можна знайти ось тут, а сам код повністю — ось тут. Запрошую до вивчення, художнього читання на різні голоси та усіляких забав з цим кодом.
Маленький такий дисклеймер — це все винятково для забави та щоби показати вам, який насправді крутий CSS.
Структура HTML
Ми будемо використовувати дуже просту HTML-розмітку для створення наших табів. Акордеону. Табордеону.
-
.tabs-container
— елемент-обгортка, який містить у собі все, що стосується нашого маленького експерименту; -
input.tab-actor
— прихована радіокнопка для керування видимістю таб-сторінки; -
label.tab-button
— зв'язаний із радіокнопкою label, що працює як таб-кнопка для перемикання між сторінками; -
.tab-content
— таб-сторінка, де можна розмістити все, що нам заманеться. Наприклад, мапу переміщення Фродо й Сема, починаючи від Ширу й аж до Судної гори. В масштабі 1:1.
Невеличкий приклад розмітки виглядає ось так:
<div class="tab-container">
<input type="radio" id="tab-1" name="tabs" class="tab-actor" checked />
<label for="tab-1" class="tab-button">Lorem ipsum</label>
<section class="tab-content">
<div class="content">…</div>
</section>
</div>
Як це працює
Головна ідея полягає в тому, щоб із допомогою псевдокласів використати просту та водночас потужну здатність елементів форми в HTML мати стан та доступ до цього стану через CSS. Для цієї витівки я використовую псевдоклас :checked
і стилізую найближчого наступного родича, увімкненого input CSS-комбінатором +
.
Аби емулювати поведінку табів, необхідно показувати лише одну активну таб-сторінку. Під активною я якраз і маю на увазі найближчого від input.tab-actor
в стані :checked
родича, а саме — .tab-content
.
Радіокнопку треба приховати, видимим має залишитися тільки пов'язаний із нею label. Той же label буде забезпечувати нам інтерактивність.
Тож, двома словами, саме так мали б працювати наші таби. Якщо вам уже дуже не терпиться, давайте напишемо базовий CSS-код.
Базовий код CSS
:root {
--tab-button-order: 1;
--tab-content-order: 10;
}
.tab-container {
display: flex;
flex-wrap: wrap;
}
.tab-actor {
display: none;
}
.tab-button {
order: var(--tab-button-order);
}
.tab-content {
order: var(--tab-content-order);
display: none;
}
.tab-actor:checked + .tab-button + .tab-content {
display: block;
}
Пропоную пройтися кожним правилом, аби зрозуміти, що ся тут коїть.
В перший день спочатку я створив CSS-змінні зі значеннями для властивості order
, саме це міститься в правилі :root
(до нього ми ще повернемося).
.tab-container {
display: flex;
flex-wrap: wrap;
}
Щоби досягти мети, ми використовуємо flex layout. Ця розкладка дає змогу використовувати будь-яку кількість табів, адже вона автоматично розподіляє всі дочірні елементи. Якби не flex, нам би довелося прописувати ширину кожного таба ручками.
За замовчуванням усі flex-елементи туляться в одному рядку, а нам потрібно, аби таб-кнопки розташовувалися зверху, а таб-сторінки — знизу. Використання flex-wrap: wrap
допоможе автоматично переносити великі елементи в наступний рядок.
<input type="radio" id="tab-1" name="tabs" class="tab-actor" checked />
<label for="tab-1" class="tab-button">Lorem ipsum</label>
Щоби зв'язати label та input, ми використаємо таку особливість HTML: якщо значення атрибуту id
в input та for
у label збігаються, то натискання на label буде активувати input, ніби натиснули прямісінько на нього.
Ось так ми ховаємо input подалі від очей, адже тицяти будемо саме на label:
.tab-actor {
display: none;
}
Далі трошки чорної магії, щоби дістати бажане розташування елементів. HTML, який ми вже написали, буде відображатися ось як:
[tab]
[content]
[tab]
Але ж потрібно нам ось що:
[tab][tab]
[content]
Отже, тут ми пускаємо в дію властивість order
, яка розташовує елементи всередині flex-розкладки у вказаному порядку, а не за фактичним розташуванням в HTML. Цей код вказує, що таб-кнопки мусять бути розміщені на початку, а таб-сторінки — в кінці.
.tab-button {
order: var(--tab-button-order);
}
.tab-content {
order: var(--tab-content-order);
display: none;
}
.tab-content
прихований за замовчуванням. Щоби показати його, ми використаємо ось такий селектор:
.tab-actor:checked + .tab-button + .tab-content {
display: block;
}
Це великий селектор, безумовно. Один із найбільших, скажу я вам. З найкращих, будьте певні. Усе приховано, а показуємо ми лише те, що прямо відповідє активованій таб-кнопці в розмітці. Буквально цей селектор мовить нам:
Покажи елемент, що іде слідом за таб-кнопкою, яка стоїть відразу після input в стані :checked
Ми використовуємо комбінатор +
, який вибирає безпосередніх сусідів, саме тому важливо, аби HTML-код слідував цьому конкретному порядку елементів.
Якщо вам не подобається такий селектор, звісно, є й інший спосіб — комбінатор ~
. Цей комбінатор обирає вже всіх наступних сусідів елемента і дає змогу скоротити селектор до такого:
.tab-actor:checked ~ .tab-content {
display: block;
}
Тут є нюанс: перша таб-кнопка активує всі-всі наступні таб-сторінки. Щоби цього уникнути, нам доведеться чітко вказувати, яка кнопка активує ту чи іншу сторінку:
/* Don't write code like this. Please. */
.tab-actor.tab-1:checked ~ .tab-content.tab-1,
.tab-actor.tab-2:checked ~ .tab-content.tab-2,
.tab-actor.tab-3:checked ~ .tab-content.tab-3,
.tab-actor.tab-4:checked ~ .tab-content.tab-4 {
display: block;
}
Так собі оптимізація, відверто кажучи.
Що ж, тепер ми маємо не те щоби дуже гарні, але працюючі таби на чистому CSS та HTML!
Ой, заграйте, музиченьки!
Бо ми зараз зробимо для вас акордеон!
Чому? Бо можемо! А ще, будьмо відверті, на маленьких екранах таби — не найкращий варіант для відображення вмісту. Тут нам у нагоді стане саме та розкладка елементів, якої ми так завзято намагалися позбутися:
Desktop:
[tab][tab]
[content]
Mobile:
[tab]
[tab]
[content]
Усе, що нам потрібно, це повернути значення властивості order
до початкового та підлаштувати розмір кнопок:
@media screen and (max-width: 480px) {
.tab-button,
.tab-content {
order: initial;
}
.tab-button {
width: 100%;
}
}
Ііііііі… Все! Це працює.
Але ждіть!
Це все, звичайно, чудово та чарівно, але виглядає якось ніяк. Пропоную трохи причепурити!
* {
margin: 0;
padding: 0;
}
body {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
max-width: 1280px;
margin-inline: auto;
}
.tab-container {
box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
justify-content: center;
}
.tab-button {
padding: 8px 16px;
border-bottom: transparent 4px solid;
transition: border-bottom-color 0.4s;
}
.content {
padding: 16px;
}
.tab-actor:checked + .tab-button {
border-bottom-color: rgb(82, 2, 136);
}
Додали трохи відступів, кольорів, жменьку анімації — і все, магія сталась! Тепер таби виглядають на відмінно. Але ви, певно, помітили, що сторінки перемикаються дуже нудно. Без іскри, знаєте. Давайте підкинемо хмизу в багаття?
@media screen and (max-width: 480px) {
.tab-button {
border-bottom: 1px solid #ccc;
transition: none;
}
.tab-content {
background-color: ivory;
}
.tab-container.full-height {
height: 100vh;
flex-direction: column;
}
.tab-container.full-height .tab-content {
display: block;
height: auto;
flex: 0;
overflow: hidden;
transition: 300ms flex;
}
.tab-container.full-height .tab-actor:checked + .tab-button + .tab-content {
flex: 1;
}
}
Що це ми такого тут написали? Додали клас .full-height
до нашого .tab-container
і присипали дрібочкою стилів.
.tab-container.full-height {
height: 100vh;
flex-direction: column;
}
Ось тут ми кажемо нашому акордеону займати всю висоту екрану і впорядковуємо дочірні елементи в колонку.
.tab-container.full-height .tab-content {
display: block;
height: auto;
flex: 0;
overflow: hidden;
transition: 300ms flex;
}
.tab-container.full-height .tab-actor:checked + .tab-button + .tab-content {
flex: 1;
}
А зараз трішки чаклуємо над .tab-content
, щоби частини розгорталися і згорталися з анімацією, яка тішить око та гріє душу.
Та й по-всьому
Й ось так, друзі, я зустрів… Перепрошую, ось так без жодного рядочка JS ми зробили таби, що можуть ставати акордеоном на льоту.
Дякую всім за увагу, шануймося!
За редактури @Ulyanka_A
Ще немає коментарів