Я (и, как мне кажется, многие из вас) сталкивался не раз с несовместимостью селектов с дизайном сайта.
Боль состоит в том, что их нельзя стилизовать, а в каждом браузере они выглядят по-своему.
Конечно, есть огромное количество решений, представляемых фреймворками/библиотеками (тот же бутстрап).
Но все они предполагают наличие JSа.
Разумеется, в этом нет ничего страшного/плохого, но я попробовал сделать стилизуемый селект без JS в качестве фоллбэка на случай, если js по каким-либо причинам сломается.
Select
Выбор инструментов
Под инструментами в данном контексте я подразумеваю то, чем мы будем заменять селект.
И мой выбор пал на radio кнопки по причине их схожего поведения: можно выбрать одновременно только один вариант.
<div class="options"> <label> <input type="radio" 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 приветствуются, без сомнения!
ссылка на оригинал статьи https://habrahabr.ru/post/313958/
Добавить комментарий