Предисловие
Какое-то время назад я стал постепенно отказываться от jQuery в пользу нативного javascript. Это связано с тем, что поддержка старых браузеров перестала быть приоритетной и на первое место вышла скорость загрузки страницы. Я не смог найти минималистичный модуль вкладок с простой html разметкой – поэтому решил написать свой.
Демо, Исходный код на Github
HTML разметка
<div class="tabs">
<div class="tabs__toggle tabs__toggle_active">Вкладка 1</div>
<div class="tabs__toggle">Вкладка 2</div>
<div class="tabs__tab">
Содержимое первой вкладки
</div>
<div class="tabs__tab">
Содержимое второй вкладки
</div>
</div>
Если на одной странице нужно разместить несколько групп вкладок нужно просто разделить их в разные блоки '.tabs'. Расположение внутренних блоков влияет только на порядок их вывода. Вкладке по умолчанию следует добавить класс 'tabs__toggle_active'.
Класс закладки
class Tab {
constructor (tabs, toggle, tab) {
this.tabs = tabs;
this.toggle = toggle;
this.tab = tab;
this.init();
}
init () {
if (this.toggle.classList.contains('tabs__toggle_active')) {
this.open();
} else {
this.close();
}
this.toggle.addEventListener('click', () => {
this.open();
});
}
open () {
if (this.tabs.active === this) {
// already open
return;
}
if (this.tabs.active) {
this.tabs.active.close();
}
this.tabs.active = this;
this.tab.style.display = 'block';
this.toggle.classList.add('tabs__toggle_active');
}
close () {
this.tab.style.display = 'none';
this.toggle.classList.remove('tabs__toggle_active');
}
}
Конструктор принимает родительский класс группы вкладок, DOM элемент кнопки по которой открывается вкладка и DOM вкладки, к которой относится данная кнопка.
Функция init проверяет, является ли эта вкладка открытой по умолчанию и добавляет событие открытия по клику.
Функция open закрывает открытую вкладку при ее наличии и устанавливает ссылку на собственный экземпляр класса в свойство 'active' родительского класса. Так же проставляет активный класс для стилизации кнопки и свойство 'display' вкладки.
Функция close убирает активный класс с кнопки и скрывает вкладку.
Класс группы вкладок
export class Tabs {
constructor (container) {
this.container = container;
this.init();
}
init () {
this.toggles = this.container.querySelectorAll('.tabs__toggle');
this.tabs = this.container.querySelectorAll('.tabs__tab');
if (!this.isEverythingOk()) {
return;
}
for (let index = 0; index < this.toggles.length; index++) {
new Tab (this, this.toggles[index], this.tabs[index]);
}
}
isEverythingOk () {
if (this.toggles.length !== this.tabs.length) {
console.warn('Tabs toggles and tabs amounts are not matching');
return false;
} else if (this.toggles.length === 0) {
console.warn('There\'s no toggles for tabs');
return false;
} else if (this.tabs.length === 0) {
console.warn('There\'s no content tabs');
return false;
}
return true;
}
}
Конструктор принимает DOM объект группы вкладок (в нашем случае .tabs).
Функция init проходит циклом по всем кнопкам и создает экземпляры класса Tab группируя по принципу «первая кнопка к первой вкладке».
Функция isEverythingOk проверяет соответствие количества вкладок количеству кнопок и их наличие, в противном случае выбрасывает предупреждение в консоль для более удобного поиска ошибок.
Функция инициализации
export default function initTabs(selector) {
for (let container of document.querySelectorAll(selector)) {
new Tabs(container);
}
}
Функция предназначена для тех, кто не хочет разбираться с принципами работы с DOM или же просто для удобства. Создает экземпляры класса Tabs.
Пример с использованием функции инициализации
import initTabs from 'future-tabs';
initTabs('.tabs');
Пример работы напрямую с классом
import {Tabs} from 'future-tabs';
const container = document.querySelector('.tabs');
const tabs = new Tabs(container);
В планах сделать выборку внутренних блоков в зависимости от селектора, дописывая названия элементов следуя методологии _bem.
Github
Спасибо за внимание!
P.S. Сделал опцию названия блока по _bem. Подробности в документации на гитхабе.
Комментарии (13)
xamd
26.08.2015 17:55Ну, признаться, Вы кроме классов, блочных переменных и деструкторов ничего не используете. Возможно, статья была бы более информативна, если бы вы параллельно рассмотрели реализацию на ES5 и более очевидно указали бы на выигрыш, который программист получает, используя новый синтаксис.
Но пример выбран, на мой взгляд, не самым удачным образом.
romy4
26.08.2015 20:16Хотел уточнить, использование ключевых слов class и export default — это из какого javascript/Ecmascript?
feedborg
27.08.2015 00:42Это связано с тем, что поддержка старых браузеров перестала быть приоритетной и на первое место вышла скорость загрузки страницы
Старых — это каких? И насколько секунд сократилось время загрузки? Сколько было?
Ну и я сильно сомневаюсь, что синтаксический сахар, под названием «class» хоть что-то ускорил. Без тестов мои слова ничего не стоят, впрочем, как и ваш топик.prog666
27.08.2015 00:53+1Насколько секунд сократилось время загрузки?
Дело же не в es6, дело в отсутствии необходимости загружать jquery, предыдущий мой такой модуль был плагином для него (jquery).
Старых — это каких?
Старых это тех которые не поддерживают такие вещи как document.querySelector и classList, хотя для последнего есть полифил ссылка на который есть в readme на github, для querySelector возможно тоже есть полифил.
Ну и я сильно сомневаюсь, что синтаксический сахар, под названием «class» хоть что-то ускорил.
По поводу скорости работы — дело в работе с DOM напрямую а не через оболочку jquery. Весь этот синтаксический сахар конечно потом транслируется в обычный es5 с помощью babel, там даже в репозитории есть уже готовый es5 файл, но подключать его все равно нужно с помощью browserify или подобной библеотеки для работы с commonjs модулями (что-то слышал про webpack но не пробовал пока что)
stcherenkov
Если для вас неактуальна поддержка старых браузеров, то есть решение на чистом HTML+CSS: css-tricks.com/functional-css-tabs-revisited
prog666
html разметка не такая удобная и универсальная, иногда переключатели и вкладки должны находится в отдельных контейнерах. Подобные решения на HTML+CSS нужны скорее для демонстрации возможностей чем для практического использования.
romy4
В приведённом выше решении расположить табы и вкладки в отдельных контейнерах как раз не составит труда. Label можно вынести куда угодно, а скрытый radio и контейнер останутся на одном уровне.
prog666
Согласен, но еще остается необходимость прописывать для каждой вкладки уникальные названия.