Идея заключается в том, чтобы превратить классическую и набившую оскомину форму в текстовое предложение, меняя отдельные слова которого, можно выяснить предпочтения пользователи и в соответствии с ними предоставить нужную информацию. Подобного рода формы подходят не для всех интерфейсов, но если вам удастся органично вписать ее в свой дизайн - вы получите настоящую бомбу! Итак, начнем.
Наш HTML-код содержит тег form с поисковой фразой, отдельные слова которой являются элементами выбора select с несколькими вариантами. А в конце предложения будет поле для ввода input. Также нам понадобятся два блока div: один для кнопки button, рядом с полем для ввода текста, а второй для затемнения экрана при нажатии на какой-нибудь элемент выбора.
<form id="nl-form" class="nl-form">
Я хочу поесть
<select>
<option value="1" selected>любой еды</option>
<option value="2">Грузинскую кухню</option>
<option value="3">Французскую кухню</option>
<option value="4">Японскую кухню</option>
<option value="5">Итальянскую кухню</option>
</select>
<br> и выпить
<select>
<option value="1" selected>что-нибудь</option>
<option value="2">сок</option>
<option value="3">вино</option>
<option value="4">чаю</option>
<option value="5">минералку</option>
</select>
в ресторане
<br>
<select>
<option value="1" selected>в любое время</option>
<option value="2">в 19:00</option>
<option value="3">в 20:00</option>
<option value="4">в 21:00</option>
</select>
в <input type="text" value="" placeholder="любом городе" data-subline="Для примера: <em>Москва</em> или <em>Париж</em>">
<div class="nl-submit-wrap">
<button class="nl-submit" type="submit">Найти рестораны</button>
</div>
<div class="nl-overlay"></div>
</form>
У нас есть базовая разметка, теперь нам нужно превратить элементы выбора в нестандартный выпадающий список:
<div class="nl-field nl-dd">
<a class="nl-field-toggle">что-нибудь</a>
<ul>
<li class="nl-dd-checked">что-нибудь</li>
<li>сок</li>
<li>вино</li>
<li>чаю</li>
<li>минералку</li>
</ul>
</div>
По-умолчанию этот список скрыт, а при нажатии на переключатель будет плавно показываться. Нажимая на элементы списка мы будем заменять значение переключателя на то, которое выбрали. В роли переключателей у нас элементы выбора select
. Поле для ввода текста будет трансформировано в похожий элемент:
<div class="nl-field nl-ti-text">
<a class="nl-field-toggle">любом городе</a>
<ul>
<li class="nl-ti-input">
<input type="text" value="" placeholder="любом городе">
<button class="nl-field-go">Поехали</button>
</li>
<li class="nl-ti-example">Для примера: <em>Москва</em> или <em>Париж</em></li>
</ul>
</div>
Примечание: Не нужно сейчас же вручную менять разметку, мы это сделаем с помощью javascript
Пришла пора поработать над CSS. Для начала подключим шрифы с иконками, которые мы будем использовать (галочку и стрелку):
@font-face {
font-family: 'nlicons';
src:url('/font/nlicons/nlicons.eot');
src:url('/font/nlicons/nlicons.eot?#iefix') format('embedded-opentype'),
url('/font/nlicons/nlicons.woff') format('woff'),
url('/font/nlicons/nlicons.ttf') format('truetype'),
url('/font/nlicons/nlicons.svg#nlicons') format('svg');
font-weight: normal;
font-style: normal;
}
Запишем основные стили для нашей формы
/* CSS стили для формы */
.nl-form {
width: 100%;
margin: 0.3em auto 0 auto;
font-size: 4em;
line-height: 1.5;
}
Сбросим дефолтные браузерные стили для списков:
.nl-form ul {
list-style: none;
margin: 0;
padding: 0;
}
Также сбрасываем стили для всех элементов выбора и ввода
/* Сброс стилей для элементов формы */
.nl-form input,
.nl-form select,
.nl-form button {
border: none;
background: transparent;
font-family: inherit;
font-size: inherit;
color: inherit;
font-weight: inherit;
line-height: inherit;
display: inline-block;
padding: 0;
margin: 0;
-webkit-appearance: none;
-moz-appearance: none;
}
Мы не хотим чтобы выбранные элементы подсвечивались (знаете, такая противная рамочка из точек), поэтому запишем следующий стиль:
.nl-form input:focus {
outline: none;
}
Теперь давайте подумаем о пользовательских нестандартных полях. Мы дали этим полям собственный класс nl-field
/* Стилизация нестандартных полей (выпадающий список, текстовые элементы) */
.nl-field {
display: inline-block;
position: relative;
}
Используя значение inline-block
мы расположим наши элементы как обычные слова в предложении. Когда мы нажимаем на переключатель, список должен быть поверх всех элементов, поэтому установим большое значение свойства z-index:
.nl-field.nl-field-open {
z-index: 10000;
}
Сам переключатель (видимый элемент в форме) будет иметь следующие стили:
/* Переключатель */
.nl-field-toggle,
.nl-form input,
.nl-form select {
line-height: inherit;
display: inline-block;
color: #b14943;
cursor: pointer;
border-bottom: 1px dashed #b14943;
}
Данные стили будут установлены по умолчанию, если JS будет отключен. Наш неупорядоченный список будет скрыт и иметь абсолютное позиционирование с помощью CSS свойства position: absolute
. Мы будем использовать значение visibility свойства transition для того, чтобы плавно регулировать непрозрачность списка. Установим небольшую задержку в изменении непрозрачности, когда список исчезает, а при появлении списка задержки не будет.
/* Выпадающий список / Текстовые элементы */
.nl-field ul {
position: absolute;
visibility: hidden;
background: #76C3BD;
left: -0.5em;
top: 50%;
font-size: 80%;
opacity: 0;
-webkit-transform: translateY(-40%) scale(0.9);
-moz-transform: translateY(-40%) scale(0.9);
transform: translateY(-40%) scale(0.9);
-webkit-transition: visibility 0s 0.3s, opacity 0.3s, -webkit-transform 0.3s;
-moz-transition: visibility 0s 0.3s, opacity 0.3s, -moz-transform 0.3s;
transition: visibility 0s 0.3s, opacity 0.3s, transform 0.3s;
}
.nl-field.nl-field-open ul {
visibility: visible;
opacity: 1;
-webkit-transform: translateY(-50%) scale(1);
-moz-transform: translateY(-50%) scale(1);
transform: translateY(-50%) scale(1);
-webkit-transition: visibility 0s 0s, opacity 0.3s, -webkit-transform 0.3s;
-moz-transition: visibility 0s 0s, opacity 0.3s, -moz-transform 0.3s;
transition: visibility 0s 0s, opacity 0.3s, transform 0.3s;
}
Теперь зададим стили для элементов списка:
.nl-field ul li {
color: #fff;
position: relative;
}
.nl-dd ul li {
padding: 0 1.5em 0 0.5em;
cursor: pointer;
white-space: nowrap;
}
.nl-dd ul li.nl-dd-checked {
color: #478982;
}
.no-touch .nl-dd ul li:hover {
background: rgba(0,0,0,0.05);
}
.no-touch .nl-dd ul li:hover:active {
color: #478982;
}
Теперь добавим иконки:
/* Добавляем иконки */
.nl-dd ul li.nl-dd-checked:before,
.nl-submit:before,
.nl-field-go:before {
font-family: 'nlicons';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
-webkit-font-smoothing: antialiased;
}
.nl-dd ul li.nl-dd-checked:before {
content: "\e000";
position: absolute;
right: 1em;
font-size: 50%;
line-height: 3;
}
Так же, полю для ввода зададим минимальную ширину:
.nl-ti-text ul {
min-width: 8em;
}
Само поле для ввода будет иметь следующие стили:
.nl-ti-text ul li.nl-ti-input input {
width: 100%;
padding: 0.2em 2em 0.2em 0.5em;
border-bottom: none;
color: #fff;
}
Кнопке со стрелкой дадим абсолютное позиционирование и расположим справа от поля для ввода.
.nl-form .nl-field-go {
position: absolute;
right: 0;
top: 0;
height: 100%;
cursor: pointer;
background: rgba(0,0,0,0.1);
width: 1.8em;
text-align: center;
color: transparent;
}
Добавим на кнопку иконку стрелки:
.nl-field-go:before {
content: "\e001";
font-size: 75%;
color: #fff;
width: 100%;
line-height: 2.5;
display: block;
}
Изменим цвет у атрибута plactholder, чтобы он гармонировал со всем остальным:
/* Изменяем цвет для placeholder */
input::-webkit-input-placeholder {
color: rgba(255,255,255,0.8);
}
input:active::-webkit-input-placeholder ,
input:focus::-webkit-input-placeholder {
color: rgba(255,255,255,0.2);
}
input::-moz-placeholder {
color: rgba(255,255,255,0.8);
}
input:active::-moz-placeholder,
input:focus::-moz-placeholder {
color: rgba(255,255,255,0.2);
}
input:-ms-input-placeholder {
color: rgba(255,255,255,0.8);
}
input:active::-ms-input-placeholder ,
input:focus::-ms-input-placeholder {
color: rgba(255,255,255,0.2);
}
Текст-подсказка под полем для ввода названия города будет иметь меньший размер шрифта:
/* Поле с примером в форме ввода города */
.nl-ti-text ul li.nl-ti-example {
font-size: 40%;
font-style: italic;
padding: 0.4em 1em;
color: #4c4c4c;
border-top: 1px dashed rgba(255,255,255,0.7);
}
.nl-ti-text ul li.nl-ti-example em {
color: #fff
}
Настала очередь заняться кнопкой отправки нашей формы. У нее также будет иконка стрелки.
/* Кнопка подтверждения */
.nl-submit-wrap {
margin-top: 0.4em;
}
.nl-form .nl-submit {
line-height: 3;
text-transform: uppercase;
cursor: pointer;
position: relative;
background: #76C3BD;
color: #fff;
padding: 0 1em 0 0;
font-size: 40%;
font-weight: 700;
letter-spacing: 3px;
}
.nl-submit:before {
content: "\e001";
color: #fff;
float: left;
padding: 0 0.7em;
margin: 0 0.8em 0 0;
background: #69B1A9;
}
.nl-form .nl-submit:hover,
.nl-form .nl-submit:active {
background: #69B1A9;
}
.nl-form .nl-submit:hover:before {
background: #58a199;
}
При открытии списка на фон накладывается элемент с классом nl-overlay
, который имеет абсолютное позиционирование и залит полупрозрачным черным цветом. Таки образом достигается эффект затемнения фона. Мы используем селектор родства ~, чтобы затемнить фон и, одновременно, показать список.
/* Слой с перекрытием появляется, когда список открывается */
.nl-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
opacity: 0;
z-index: 9999;
visibility: hidden;
-webkit-transition: visibility 0s 0.3s, opacity 0.3s;
-moz-transition: visibility 0s 0.3s, opacity 0.3s;
transition: visibility 0s 0.3s, opacity 0.3s;
}
.nl-field.nl-field-open ~ .nl-overlay {
opacity: 1;
visibility: visible;
-webkit-transition-delay: 0s;
-moz-transition-delay: 0s;
transition-delay: 0s;
}
Атеперь, позаботимся о наших самых маленьких.. нет, не зрителях), а экранах, для чего увеличим размер шрифта с помощью CSS свойства font-size и медиа запросах @media:
@media screen and (max-width: 45em) {
.nl-form {
font-size: 3em;
}
}
@media screen and (max-width: 25em) {
.nl-form {
font-size: 2em;
}
}
Супер, со стилями закончили. Теперь дело за JavaScript! Создадим файл скрипта и подключим его к HTML странице:
<script src="js/custom.js"></script>
Определим объект, представляющий элементы формы и несколько переменных:
function NLForm(el){
// элемент формы
this.el = el;
// блок наложения
this.overlay = this.el.querySelector( '.nl-overlay' );
// массив со всеми возможными нестандартными полями
this.fields = [];
// счетчик для каждого поля
this.fldOpen = -1;
this._init();
}
Начнем с того, что создадим свою структуру, заменяя стандартные элементы выбора и ввода. Для этого создадим объект – NLField.
NLForm.prototype = {
_init : function() {
var self = this;
Array.prototype.slice.call(this.el.querySelectorAll('select')).forEach(function(el, i){
self.fldOpen++;
self.fields.push(new NLField( self, el,'dropdown',self.fldOpen));
} );
Array.prototype.slice.call(this.el.querySelectorAll('input')).forEach( function(el,i){
self.fldOpen++;
self.fields.push(new NLField( self, el,'input',self.fldOpen));
} );
this.overlay.addEventListener('click', function(ev){self._closeFlds();});
this.overlay.addEventListener('touchstart',function(ev){self._closeFlds();});
},
_closeFlds : function() {
if( this.fldOpen !== -1 ) {
this.fields[ this.fldOpen ].close();
}
}
}
function NLField(form, el, type, idx){
this.form = form;
// стандартные элементы HTML
this.elOriginal = el;
this.pos = idx;
this.type = type;
this._create();
this._initEvents();
}
Структура будет отличаться в зависимости от того, что за элемент – select
или input
. Также мы будем привязывать некоторые события к нашим полям. Выпадающее меню должно обновлять форму в зависимости от того, какой элемент меню выбран, а в поле ввода пользователь должен ввести какой-нибудь текст и нажать кнопку или просто кликнуть на фоне, чтобы введенное значение обновилось в форме.
NLField.prototype = {
_create : function() {
if( this.type === 'dropdown' ) {
this._createDropDown();
}
else if( this.type === 'input' ) {
this._createInput();
}
},
_createDropDown : function() {
var self = this;
this.fld = document.createElement( 'div' );
this.fld.className = 'nl-field nl-dd';
this.toggle = document.createElement( 'a' );
this.toggle.innerHTML = this.elOriginal.options[ this.elOriginal.selectedIndex ].innerHTML;
this.toggle.className = 'nl-field-toggle';
this.optionsList = document.createElement( 'ul' );
var ihtml = '';
Array.prototype.slice.call(this.elOriginal.querySelectorAll('option')).forEach(function(el,i){
ihtml += self.elOriginal.selectedIndex === i ? '<li class="nl-dd-checked">'+el.innerHTML+
'</li>' : '<li>' + el.innerHTML + '</li>';
// индекс элемента
if( self.elOriginal.selectedIndex === i ) {
self.selectedIdx = i;
}
} );
this.optionsList.innerHTML = ihtml;
this.fld.appendChild(this.toggle );
this.fld.appendChild(this.optionsList);
this.elOriginal.parentNode.insertBefore( this.fld, this.elOriginal);
this.elOriginal.style.display = 'none';
},
_createInput : function() {
var self = this;
this.fld = document.createElement('div');
this.fld.className = 'nl-field nl-ti-text';
this.toggle = document.createElement( 'a' );
this.toggle.innerHTML = this.elOriginal.getAttribute('placeholder');
this.toggle.className = 'nl-field-toggle';
this.optionsList = document.createElement('ul');
this.getinput = document.createElement('input');
this.getinput.setAttribute( 'type', 'text' );
this.getinput.setAttribute( 'placeholder', this.elOriginal.getAttribute('placeholder'));
this.getinputWrapper = document.createElement('li');
this.getinputWrapper.className = 'nl-ti-input';
this.inputsubmit = document.createElement('button');
this.inputsubmit.className = 'nl-field-go';
this.inputsubmit.innerHTML = 'Go';
this.getinputWrapper.appendChild(this.getinput);
this.getinputWrapper.appendChild(this.inputsubmit);
this.example = document.createElement('li');
this.example.className = 'nl-ti-example';
this.example.innerHTML = this.elOriginal.getAttribute('data-subline');
this.optionsList.appendChild(this.getinputWrapper);
this.optionsList.appendChild(this.example);
this.fld.appendChild(this.toggle);
this.fld.appendChild(this.optionsList );
this.elOriginal.parentNode.insertBefore(this.fld, this.elOriginal);
this.elOriginal.style.display = 'none';
},
_initEvents : function() {
var self = this;
this.toggle.addEventListener('click',function(ev) {
ev.preventDefault(); ev.stopPropagation(); self._open();
});
this.toggle.addEventListener('touchstart', function(ev){
ev.preventDefault(); ev.stopPropagation(); self._open();
});
if(this.type === 'dropdown'){
var opts = Array.prototype.slice.call(this.optionsList.querySelectorAll('li'));
opts.forEach( function(el, i){
el.addEventListener('click', function(ev){
ev.preventDefault(); self.close(el, opts.indexOf(el));
});
el.addEventListener( 'touchstart', function(ev) {ev.preventDefault(); self.close(el, opts.indexOf(el)); });
});
}
else if( this.type === 'input' ) {
this.getinput.addEventListener('keydown', function(ev) {
if (ev.keyCode == 13) {
self.close();
}
});
this.inputsubmit.addEventListener('click', function(ev){ev.preventDefault(); self.close();});
this.inputsubmit.addEventListener('touchstart', function(ev){ev.preventDefault(); self.close();});
}
},
_open : function(){
if( this.open ){
return false;
}
this.open = true;
this.form.fldOpen = this.pos;
var self = this;
this.fld.className += ' nl-field-open';
},
close : function(opt, idx){
if(!this.open) {
return false;
}
this.open = false;
this.form.fldOpen = -1;
this.fld.className = this.fld.className.replace(/\b nl-field-open\b/,'');
if(this.type === 'dropdown'){
if(opt) {
// удаляем класс nl-dd-checked из предыдущего элемента
var selectedopt = this.optionsList.children[this.selectedIdx];
selectedopt.className = '';
opt.className = 'nl-dd-checked';
this.toggle.innerHTML = opt.innerHTML;
// обновляем индекс поля <select>
this.selectedIdx = idx;
// обновляем значение <select>
this.elOriginal.value = this.elOriginal.children[this.selectedIdx].value;
}
}
else if( this.type === 'input'){
this.getinput.blur();
this.toggle.innerHTML = this.getinput.value.trim() !== '' ? this.getinput.value : this.getinput.getAttribute('placeholder');
this.elOriginal.value = this.getinput.value;
}
}
}
Последним шагом объявляем глобальную переменную
// Объявляем глобальную переменную NLForm
window.NLForm = NLForm;
и передаем в нее нашу форму из HTML-документа
<script>
var nlform = new NLForm( document.getElementById('nl-form') );
</script>
Вот и мы и создали форму для поиска. Быстрее внедряйте, пока не остыла))