Select / Multiselect без JS

Я (і, як мені здається, багато хто з вас стикався не раз з несумісністю селектов з дизайном сайту. Біль полягає в тому, що їх не можна стилізувати, а в кожному браузері вони виглядають по-своєму.
Звичайно, є величезна кількість рішень, що подаються фреймворками/бібліотеками (той же бутстрап). Але всі вони припускають наявність ЈЅа. Зрозуміло, в цьому немає нічого страшного/поганого, але я спробував зробити стилизуемый селект без JS як фоллбэка на випадок, якщо js з якихось причин зламається.
Select
Вибір інструментів
Під інструментами в даному контексті я маю на увазі те, чим ми будемо замінювати селект. І мій вибір припав на radio кнопки з причини їх схожої поведінки: можна вибрати тільки один варіант.
<div class="options">
<label>
<input type="галичина" name="r" value="111" checked>
<div class="value">11text11</div>
</label>
....

Ми обернули радіо-кнопки <label/>, щоб не створювати непотрібних IDшников і for-ів.
Логіка
Для реалізації вибору потрібного пункту як в селекте нам треба, щоб він опинявся у верхній позиції списку. Для цього позначимо, що значення вибраного елемента має позиціонуватися абсолютно (вириватися з потоку) і поміщатися на самий верх:
#dropdown .options :checked + .value{
position: absolute;
top: 0;
}

Також нам треба дотримати правило: висота кожного пункту повинна бути дорівнює "вакантному" місця (відступу зверху) для вибраного пункту
#dropdown .options .value{
height: var(--item-height);
}
#dropdown .options{
padding-top: var(--item-height);
}

Основна частина працює — при виборі пункту він виявляється на лідируючій позиції. Залишилося турбуватися тим, щоб в "спокійному" стан був видний тільки вибраний пункт, а інший список розкривався при кліці, також при кліці в "молоко" список повинен закриватися без зміни значення.
На розум приходить маніпуляція псевдоселектором :focus. Додамо який-небудь инпут, який буде під нього потрапляти. Я вибрав [type=text] тому, що йому можна задати розмір (розтягнути на всю ширину і висоту) і затулити їм лідируючий вибраний елемент.
<div class="dropdown" id="dropdown">
<input type="text">
<div class="options">
....

Приховувати випадаючий список будемо обмеженням висоти і overflow: hidden:
#dropdown .options{
padding-top: var(--item-height);
overflow: hidden;
height: 0;
}
#dropdown > :focus + .options{
height: var(--list-height);
}

Зрозуміло, инпута не повинно бути видно (як і радіо-кнопок, що представляють опції):
#dropdown input{
opacity: 0;
}

Зауваження!Слід використовувати opacity: 0; замість display: none; з причини того, що у зритих елементів (visually: hidden в тому числі) не може бути стану :focus.
Брудний хак
І коли вже здавалося, що все вийшло, я зіткнувся з несподіванкою: при кліці по пункту меню зі списку фокус з керуючого инпута йде швидше, ніж спрацьовує клік на елементі списку. Тобто відбувається рівно те ж, що при кліці на молоко — список просто закривається.
Щоб збільшити затримку до приховування випав списку, доведеться використовувати брудний хак:
#dropdown .options{
...
transition: 0s .1s height;
}

Готово, дивіться приклад (я додав трохи стилів для краси): https://jsfiddle.net/2k1pvbyt/
Мультиселект
Якщо ми підемо далі, то нам захочеться зробити таким же чином (без JS) мультиселект. Не кожен інтегратор jquery-плагінів такий зробить з JS (JQuery), а ми-то з вами бач чого надумали! Ну сказано — зроблено, не можна впасти обличчям в бруд. Спробуємо розібратися, чи це можливо. І якщо ні, що саме моменті не можна обійтися без js.
Що нам треба змінити в попередньому прикладі, щоб наш селект став мульти? Кожен пункт повинен мати можливість бути обраним незалежно від інших. Очевидно, нам треба змінити радіо-кнопки на чекбокси:
<label data value="111">
<input type="checkbox" required>
</label>

Так, це ще не все, і required там не випадково, з його допомогою ми будемо маніпулювати нашим списком.
<fieldset>
<label data value="111">
<input type="checkbox" required>
</label>
</fieldset>

Якщо ми обернем це у <fieldset/>, то у нас з'явиться можливість маніпулювати псевдоселекторами :valid / :invalid.
Примітка
<fieldset/>, як і <form/> матчится на селектор :valid в тому випадку, якщо всередині нього всі поля також :valid

Щоб зрозуміти, що саме ми повинні зробити, треба чітко сформулювати завдання:
Нехай виділений пункт буде на початку списку. На одному рядку з ним інші виділені пункти.
Помістити в початок списку вибраний елемент нескладно, ми задамо батькові display: flex і будемо гратися зі значенням order:
#dropdown .options > fieldset:invalid{
order: 2;
}
#dropdown .options{
display: flex;
flex-wrap: wrap;
}
#dropdown fieldset{
flex-basis: 100%;
}

Перша умова виконана і, щоб помістити всі вибрані елементи на один рядок для вибраних пунктів задамо:
#dropdown .options > fieldset:valid{
flex-basis: 10%;
z-index: 3;
}

Вуаля! схоже, що працює.
Висновок
У нас не вийшло з'ясувати, де (у рамках завдання) ми не можемо обійтися без js.
Можливо (точно), на більш складних прикладах так і буде.
Дублюю посилання на приклади:
Ну і для тих, хто не хоче писати цю "купу розмітки" руками, накидав скрипт, який зробить це за вас.
Доповнення та інші issue вітаються, без сумніву!
UPD
Прийшла в голову ідея використовувати керуючий инпут. Він стане в нагоді, якщо ми відійдемо від концепції nojs, будемо використовувати якийсь js-конструктор для ініціалізації.
Серед внесених змін:
  • При фокусі инпут ми не приховуємо, а зміщуємо нижче лідируючого пункту
  • Під цю справу збільшуємо «вакантне місце»
  • При ініціалізації вішаємо обробник на цей инпут, який на подію input генерує css-рядок, необхідну для фільтрації
» Дивитися приклад
» Інструкція та код
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.