Не так давно мой друг создал компонент пользовательского интерфейса для изменения содержимого страницы на основе текущего выбора элемента <select>
. Ее код работал, но, будучи новичком в JavaScript, она создала компонент в jQuery и попросила меня помочь оптимизировать его.
Не точно копируя код, я создал следующий пример, чтобы продемонстрировать тип компонента пользовательского интерфейса, который она пыталась создать:
<div class="select-area"> <select name="choose" id="choose" class="input-select"> <option value="nul" selected>Make a Selection</option> <option value="opt1">Option 1</option> <option value="opt2">Option 2</option> <option value="opt3">Option 3</option> </select> </div> <section class="jqueryOptions opt1"> <div class="content"> <h2>Option 1 Content</h2> <p> ... </p> </div> </section> <section class="jqueryOptions opt2"> <div class="content"> <h2>Option 2 Content</h2> <p> ... </p> </div> </section> <section class="jqueryOptions opt3"> <div class="content"> <h2>Option 3 Content</h2> <p> ... </p> </div> </section>
Немного оптимизировав его, вот jQuery, который мы использовали для переключения состояния отображения выбранного блока контента.
$(function() { $('.jqueryOptions').hide(); $('#choose').change(function() { $('.jqueryOptions').slideUp(); $('.jqueryOptions').removeClass('current-opt'); $("." + $(this).val()).slideDown(); $("." + $(this).val()).addClass('current-opt'); }); });
Так что здесь происходит?
Вышеприведенная функция jQuery ищет все блоки содержимого, имеющие класс jqueryOptions, и скрывает их по умолчанию.
Затем, когда пользователь изменяет выбранную опцию входных данных select
(которая имеет идентификатор «выбирать»), функция закрывает любые потенциально открытые блоки содержимого с помощью метода .slideUp()
а затем открывает выбранную опцию с помощью slideDown()
. Он делает это, беря значение выбранной опции (обозначается как this
) и ссылаясь на элемент с именем класса, которое соответствует значению в «this».
Так:
<option value="opt1">Option 1</option>
Спички:
<section class="options opt1"> ... </section>
И вот демо:
Или посмотрите демонстрацию работающей полной страницы здесь .
Это было не слишком сложно, а?
Так почему бы не оставить это на этом?
Проблема с решением jQuery состоит в том, что мы включили jQuery (сжатый 98 КБ) только для этого небольшого количества функциональности. Мы могли бы сделать лучше.
Не изменяя разметку, давайте посмотрим, как мы можем создать такой же эффект, сначала используя ванильный JavaScript, а затем используя только CSS.
Сначала давайте подумаем о том, что должно произойти, чтобы воссоздать этот эффект.
- Контент должен быть скрыт по умолчанию.
- Сделав выбор, нам нужно показать выбранный контент.
- Нам также нужно скрыть любой ранее открытый контент при выборе новой опции.
К этому есть некоторые дополнительные части, которые будут объяснены в коде, но это три основных момента этого скрипта, которые мы должны помнить. Один из которых, скрывая контент по умолчанию, мы можем установить по умолчанию с помощью CSS, так что это один вычеркнутый. Два, чтобы пойти.
Создание версии JavaScript Vanilla
Для начала давайте настроим несколько переменных, чтобы сделать нашу жизнь проще.
var selectInput = document.getElementById('choose'), panels = document.querySelectorAll('.options'), currentSelect, i;
Теперь у нас есть переменные для легкого доступа к нашему вводу select ( selectInput
), различным панелям содержимого ( panels
), местозаполнителю для текущего выделения и итератору.
Далее нам нужно написать некоторые функции, которые помогут нам позаботиться о пунктах из вышеупомянутого маркированного списка.
Начиная с последней маркировки сначала, мы должны убедиться, что любой контент, который был просмотрен, будет скрыт при выборе новой опции. Для этого мы создадим свою собственную функцию:
function clearShow() { for ( i = 0; i < panels.length; i++ ) { panels[i].classList.remove('show'); } }
Функция clearShow()
делает несколько вещей. Во-первых, он берет нашу переменную Panel (которая представляет собой список всех узлов на странице с классом «options») и выполняет итерацию по каждому (три в нашем случае) и удаляет класс «show» из всех них.
Класс «show» — это то, что делает контент видимым на нашей странице.
Теперь, когда у нас есть способ удалить класс «show» из каждого блока контента, нам нужен способ добавить «show» на выбранную панель.
function addShow(showThis) { var el = document.getElementsByClassName(showThis); for ( i = 0; i < el.length; i++ ) { el[i].classList.add('show'); } }
Функция addShow()
получает аргумент с именем showThis
и добавляет класс «show» к узлу панели, который имеет класс, соответствующий текущему значению входных данных select
. Теперь нам все еще нужен еще один фрагмент для передачи значения addShow()
в addShow()
.
function vUpdate() { currentSelect = selectInput.value; clearShow(); addShow(currentSelect); } selectInput.addEventListener('change', vUpdate);
Функция vUpdate()
выполняется всякий раз, когда выбранный вход обновляется (обнаруживается через событие change
). Поэтому, когда vUpdate()
запускается, он делает следующее:
- Захватывает текущее значение
selectInput
и сохраняет его в переменнойcurrentSelect
. - Выполняет функцию
clearShow
чтобы удалить любые следы.show
с панелей. - Выполняет
addShow()
, передаваяcurrentSelect
для завершения недостающего фрагмента этой функции - Назначает
.show
панели с классом, который соответствует текущему значению выбранного входа.
Вы можете проверить демо ниже, чтобы увидеть все это в действии:
Или вы можете проверить полную страницу демо здесь .
Но прежде чем мы продолжим … Если вы выбрали опцию из выбранного входа и затем обновили страницу, значение ввода может остаться, но соответствующая панель не отображается.
Мы можем исправить это, добавив следующее:
if (selectInput.value !== 'nul') { currentSelect = selectInput.value; addShow(currentSelect); }
Эта конструкция if
проверит, является ли значение selectInput
чем-то отличным от nul
. Если это так, то значение текущего выбора addShow()
функцию addShow()
, которая addShow()
при перезагрузке страницы. Удобно, чтобы исправить тот редкий случай, когда страница обновлялась, а элемент select
отображал значение, но соответствующий контент не отображался.
И, наконец, в случае, если вам требуется поддержка Internet Explorer 9 или ниже, мы не можем использовать classList()
. Чтобы обойти это, вот функции для добавления и удаления классов из элемента. Вышеприведенные демонстрации CodePen используют эти функции:
function addClass(elm, newClass) { elm.className += ' ' + newClass; } function removeClass(elm, deleteClass) { elm.className = elm.className.replace(new RegExp("\\b" + deleteClass + "\\b", 'g'), '').trim(); /* the RegExp here makes sure that only the class we want to delete, and not any other potentially matching strings get deleted. */ }
Это можно сделать только с помощью CSS?
Имея множество компонентов, которые я делаю, я хотел бы попытаться понять, насколько близко я могу их воспроизвести в чистом CSS-решении. Однако, если решения jQuery и vanilla JavaScript могут основываться на одной и той же разметке, необходимо изменить только решение CSS.
Самым большим и самым важным изменением по сравнению с предыдущими версиями является то, что select
должен быть полностью заменен. Если в предыдущих примерах использовалось значение элемента select
для изменения отображаемой панели содержимого, один только CSS не знает об этих изменениях значений и, следовательно, не может использовать разные значения для переключения различных цепочек селектора.
Элемент select
будет просто необходимо воссоздать, и для этого мы будем использовать так называемый «флажок / переключатель радиокнопок».
Вот разметка для нашего нового «элемента select
».
<input type='checkbox' class="invis" id="open_close" /> <input type='radio' name='opts' class="invis" id="opt1" /> <input type='radio' name='opts' class="invis" id="opt2" /> <input type='radio' name='opts' class="invis" id="opt3" /> <header class="header-base"> <div class="content"> <p> Choose an Option </p> <div class="select-area"> <label for="open_close" class="input-select"> ... </label> <ul class="select-options"> <li> <label for="opt1">Option 1</label> </li> <li> <label for="opt2">Option 2</label> </li> <li> <label for="opt3">Option 3</label> </li> </ul> </div> </div> </header>
То, что сделано здесь, в основном представляет собой воссоздание элемента select
насколько это позволяют HTML и CSS, без помощи JavaScript. Входные данные над заголовком действуют как элементы управления для привязок стиля для панелей select
и содержимого на сайте. И новый неупорядоченный список и метки становятся нашим новым элементом select
.
Метки для opt1, opt2 и opt3 изменяют проверенное состояние переключателей с соответствующими идентификаторами. В CSS, в зависимости от того, какая радиокнопка выбрана, соответствующая панель настраивается для отображения с использованием общего селектора родного брата.
#opt1:checked ~ main .opt1, #opt2:checked ~ main .opt2, #opt3:checked ~ main .opt3 { display: block; height: 100%; overflow: visible; visibility: visible; }
Существует множество мелких деталей для репликации элемента select
, которые зависят от соответствующей переключаемой кнопки и общего селекторного элемента CSS. Для получения дополнительной информации об общем селекторе братьев и сестер и других полезных вещах, которые вы можете сделать только с помощью CSS, вам следует ознакомиться с моими статьями « Как это можно сделать с помощью CSS?». и CSS Morphing Menu .
В этих статьях я не расскажу о некоторых более сложных аспектах воссоздания элемента select
.
Довольно просто, что мы хотим, чтобы содержимое страницы немедленно обновлялось в зависимости от того, какая опция нажата. Проблема, с которой мы сталкиваемся, состоит в том, что, если элемент select
и блоки контента основаны на одних и тех же переключателях, щелчок по элементу select
приведет к исчезновению контента. Не только это, но если бы были открыты опции select
, и мы щелкнули по одной из них, опции исчезли бы, и блок контента отобразился бы.
Чтобы устранить этот недостаток UX, я добавил <input type="checkbox" class="invis" id="open_close" />
. Теперь элемент select
и текущий контент могут быть открыты одновременно. Недостатком здесь является то, что элемент select
не закрывается при щелчке опции, а закрывается только при повторном нажатии. Не идеально. Однако я чувствовал, что это был лучший опыт, чем тот, о котором я упоминал ранее. Чтобы привлечь внимание к элементу select
нужно щелкнуть снова, чтобы закрыть, я изменил стрелку вниз на X при открытии элемента:
#open_close:checked ~ .header-base .select-area .select-options { opacity: 1; visibility: visible; } #open_close:checked ~ .header-base .select-area:after { border: none; content: 'X'; top: -24px; }
Еще одна особенность истинных элементов select
я хотел воссоздать, заключалась в том, что при выборе новой опции текст основного элемента select
изменяется на эту опцию. Используя псевдоэлемент :before
, этот текст может меняться в зависимости от того, какая радиокнопка выбрана:
.input-select:before { content: 'Make a Selection'; } #opt1:checked ~ .header-base .input-select:before { content: 'Option 1'; } #opt2:checked ~ .header-base .input-select:before { content: 'Option 2'; } #opt3:checked ~ .header-base .input-select:before { content: 'Option 3'; }
Вы можете увидеть выше и многое другое в действии, просмотрев исходный код демонстрационной версии CodePen только для CSS:
А вот и полная версия страницы .
В заключение кратко изложу плюсы и минусы…
Мы только что рассмотрели три различных способа создания аналогичного интерфейса с использованием jQuery, затем с использованием ванильного JavaScript, а затем снова с использованием только CSS. У каждой техники есть свои плюсы и минусы.
Решение jQuery , безусловно, является самым простым в реализации, имея очень мало кода.
Недостатком решения jQuery является то, что если вам нужна только библиотека для этого интерфейса, вы добавляете целую библиотеку для одной цели.
Решение Vanilla JavaScript , использующее CSS-преобразования и переходы, очень близко к решению jQuery. Сокращенный до 486 байт — очевидно, намного меньше, чем альтернатива jQuery.
Недостатком этого решения является то, что некоторые из используемых JavaScript поддерживаются не во всех браузерах, и для поддержки этих браузеров вам потребуется написать дополнительные обходные функции (как я это сделал).
Решение только для CSS отлично подходит для тех, кто хочет экспериментировать с CSS или для тех, кто недостаточно знаком с JavaScript. Однако для правильной работы требуется довольно много повторяющихся CSS-кодов, зависящих от разметки, и это может отрицательно повлиять на доступность сайта, поскольку вы должны использовать элементы так, как они не предназначены для использования.
Независимо от того, к какому решению вы стремитесь, не забудьте взвесить все «за» и «против» каждого и постараться предложить лучший пользовательский опыт.