Универсальный виджет на 17 строк JS

в 8:33, , рубрики: css, html, html-верстка, jquery, виджеты

В процессе работы над очередным Web-проектом озадачился вопросом, зачем мне нужна, большая, неповоротливая библиотека jQwery UI, с кучей мусора в виде классов и виртуальных элементов в DOM. Многие мои товарищи по оружию, fontent-разработчики и html-верстальщики часто в своей практике используют различного рода фреймворки с разной степенью загромождения документа, например, ненавистный мне bootstrap. Да, они ускоряют процесс создания проекта, но так же генерируют массу избыточного кода, много не нужных классов и конструкций вида:

 <div class=”name_1  name_2 name_3 … name_n”>

По моему скромному мнению выглядят не элегантно, я бы сказал, захламленными.
Опять же, чем легче документ с подключенными библиотеками и стилями, тем быстрее он грузится, а как следствие, выше позиция в поисковике. Мы же хотим быть в топе? Конечно хотим!

В процессе работы появилась надобность добавить спойлер на страничку, что в принципе тривиальная задача, а подключать избыточную библиотеку ой как не хотелось, начал гуглить на предмет наиболее простого решения.

Итогом поиска стал:

jQuery(document).ready(function(){
		jQuery('.spoiler-body').hide()
		jQuery('.spoiler-head').click(function(){
			jQuery(this).toggleClass("folded").toggleClass("unfolded").next().toggle()
		})
	})

Суть скрипта проста до безумия, обычный show-hide с добавлением класса folded на активный элемент. Но тут я подумал: «Черт возьми! Это же не предел его использования!».

Задача — создать кроссбраузерный виджет, семантически верный, вызываемый на странице сколь угодно раз, исполняющий роль спойлера, попапа, акордиона и табов с минимальным объемом JS и максимальной свободой работы с CSS.

После нескольких несколько итераций, и погружения в документацию jQwery скрипт приобрел следующий вид.
SPAT — Spoiler Popup Accord Tab's

$(document).ready(function () {
    $('.hidden').hide();
    $('.opnDflt').addClass('folded').next('.hidden').show();
    $('.read_more').on('click', function () {
        if ($(this).hasClass('folded')) {
            if (!($(this).attr('data-role') === 'tab')) {
                $('.hidden').hide();
                $('.folded').removeClass('folded');
                $('.opnDflt').addClass('folded').next('.hidden').show();
            }
        } else {
            $(this).parents('.spat').find('.hidden').hide();
            $(this).parents('.spat').find('.folded').removeClass('folded');
            $(this).addClass('folded').next().toggle();
        }
    });
});

Я не силен в javascript и jQwery, но я учусь. Так что возможно кто-то предложит более элегантное и(или) короткое решение задачи.

Ну а теперь по порядку, как же работает эта конструкция:

.read-more — Активный блок, собственно от него и пляшем.
.hidden — Cкрытый блок.
.opnDflt — Раскрытый блок по умолчанию, после загрузки документа.
.spat — Контейнер, ограничивающий работу скрипта, и предотвращающий вызов функции в ненужной нам части странице.
data-role=«tab» — Обозначает поведение для табов.

Спойлер

Разметка под спойлер не претерпела особых изменений от оригинала.

HTML

<dl class="spat spoiler">
<dt class="read_more">Кнопка</dt>
     <dd class="hidden spoiler-body">
           Тело окна
     </dd>
</dl>

CSS

.spoiler dt{
    background: #ececec;
    width: 5em;
    text-align: center;
    border: 1px solid;
    padding: .5em 1em;
    cursor: pointer;
}
.spoiler dt:before{
    content: "+";
    margin-right: 1em;
}
.spoiler dt.folded:before{
    content: "-";
}
.spoiler dd{
    height: 5em;
    background: red;
    margin:0;
    margin-top: 1em;
}

Просто, быстро и семантически верно. Хотя если вступать в холивары, что кнопка должна быть button, то мы потеряем преимущества списка определений.

Всплывающее окно

Тут работает тот же принцип что и у спойлера, но есть свои определённые требования к css.
Важной особенностью тут является использование псевдоэлементов :before и :after. А именно для оверлея и кнопки «закрыть» внутри окна.

HTML

<div class="spat popup">
      <button class="read_more" type="button">
            Кнопка
      </button>
      <div class="hidden popup_body">
            Тело окна
      </div>
</div>

CSS

.popup>button{
    padding: .5em 1em;
    font-size: 1em;
    cursor: pointer;
}
/* Наш оверлей с закрытием по клику*/
.popup>button.folded:after{
    content: '' ;
    position: fixed;
    height: 100%;
    width: 100%;
    left: 0;
    top: 0;
    display: block;
    background: #000;
    opacity: .5;
    cursor: pointer;
}
/* Кнопка закрыть */
.popup>button.folded:before{
    content: 'x';
    position: fixed;
    z-index: 2;
    background: #ececec;
    height: 1em;
    width: 1em;
    line-height: 1em;
    border: 1px solid;
    top: 50%;
    left: 50%;
    margin-top: -4em;
    margin-left: 8em;
    cursor: pointer;
}
.popup>.popup_body{
    position: fixed;
    z-index: 1;
    background: #fff;
    top: 50%;
    left: 50%;
    height: 10em;
    width: 20em;
    margin-left: -10em;
    margin-top: -5em;
    text-align: center;
    line-height: 10em;
}

Опять же ничего сложного, прием абсолютного позиционирования на position:fixsed, и немного магии z-index.
Если же у нас нет возможности точно знать ширину popup блока, или необходимо сделать кнопку «закрыть» в разметке, то можно поступить так:

Добавим под основную функцию код:

$('.close').on('click', function (e) {
        e.preventDefault();
        $(this).parents('.hidden').prev('.folded').trigger('click');
    });

И с чистой совестью размещаем внутри .hidden:

<button class="close">Закрыть</button>

Аккордеон

Вот тут уже начинают работать дополнения в скрипте .opnDflt и .spat.

HTML

<dl class="spat accrd">
     <dt class="read_more opnDflt">Пункт 1</dt>
     <dd class="hidden">Тело пункта 1</dd>
     <dt class="read_more">Пункт 2</dt>
     <dd class="hidden">Тело пункта 2</dd>
     <dt class="read_more">Пункт 3</dt>
     <dd class="hidden">Тело пункта 3</dd>
</dl>

CSS

.accrd dt{
    background: #ececec;
    cursor:pointer;
}
.accrd dt:first-child{
    border-top: 1px solid;
}
.accrd dt:before{
    content: '+';
    margin-right: 1em;
    width: 1em;
    display: inline-block;
}
.accrd dt.folded:before{
    content: '-'
}
.accrd dd, .accrd dt{
    padding: .5em 1em;
    border: 1px solid;
    border-top: 0px;
    margin: 0;
}

Тут все просто, развиваем принцип работы спойлера, но добавим к этому открый по умолчанию блок (.opnDflt) и закрытие всех .hidden при клике. Просто и легко. Псевдоблок :before опять же используется для обозначения открытогозакрытого элемента, можно с ним, можно без него. Если очень хочется? Хоть иконку ставь.

Табы

Пожалуй, самое сложное решение было для именно этого виджета. Очень долго ломал голову как правильно спозиционировать элементы, но спасибо вот этому комментарию, функционал был реализован с небольшими дополнениями в код jQwery плагина.
Разметка такая же, как и у аккорда, за исключением добавления атрибута data-role='tab', чтобы это добро функционировало как нужно, и clearfix — чтобы остановить float.

HTML

<dl class="spat tab">
     <dt class="read_more opnDflt" data-role="tab">Пункт 1</dt>
     <dd class="hidden">Тело пункта 1</dd>
     <dt class="read_more" data-role="tab">Пункт 2</dt>
     <dd class="hidden" >Тело пункта 2</dd>
     <dt class="read_more" data-role="tab">Пункт 3</dt>
     <dd class="hidden">Тело пункта 3</dd>
</dl>

CSS

dl.tab:after {
    content: ".";
    display: block;
    height: 0;
    clear: both;
    visibility: hidden;
    width: 100%;
}
.tab dt.folded{
    color: #000;
    font-weight: 600;
}
.tab dt {
    float: left;
    color: #c5c5c5;
    padding: .5em 1em;
    cursor: pointer;
    margin: 0 .3em;
    display: inline-block;
    border: 1px solid;
    border-bottom: 0px;
}
.tab dd{
    float: right;
    width: 100%;
    margin: 2em 0 0 -100%; /* вся магия заключена тут */
    padding: 1em;
    background: #EFEBF9;
    box-sizing: border-box;
}

Демо

В планах на будущее реализовать карусель и слайдер на основе этого виджета.
По поводу ошибок в ЛС, пожалуйста.

Используемые материалы

Исходник
Идея табов

Автор: Grim_dev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js