Создайте собственные раскрывающиеся панели для размещения контента

В данной статье описывается, как без использования сторонних библиотек при помощи HTML, CSS и JavaScript создать панель расширения. Вот как этот элемент интерфейса выглядит в действии.

Подходы

Для создания панелей расширения используется пару подходов:

  1. На основе анимации и переходов, примененных к свойствам height или max-height содержимого.
  2. Использование transform: translateY для перемещения элементов на новую позицию, создания эффекта закрытия панели и повторной визуализации DOM.
  3. Применение сторонней библиотеки.

Какой из подходов лучше?

С точки зрения производительности использование transform более эффективно, чем анимация height и max-height. При применении CSS-свойства transform элементы растризуются и перемещаются графическим процессором. Это низко затратная и простая операция для графического процессора.

Для реализации данного подхода необходимо выполнить следующие действия:

  1. Приобрести высоту контента, который будет располагаться на панели.
  2. Переместить контент выше на высоту содержимого, которое будет свернуто при помощи transform: translateY(Xpx). При помощи перехода реализовать эффект открытия и закрытия панели.
  3. При помощи JavaScript перехватить событие transitionend. После его наступления задаем display: none для контента и удаляем преобразование.

Но у данного способа есть несколько недостатков. Например, при использовании transform: translateY надо учитывать z-index элемента.

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

Применение переходов к max-height работает не так отлично, как свойство transform. Поскольку браузер изменяет высоту сворачивающегося элемента на протяжении всего перехода. Эта операция потребляет много ресурсов памяти и графического процессора. Но зато данный подход проще в реализации.

Подход на основе элементов details и summary

В HTML существуют элементы details и summary, которые могут создать панель расширения:

<details> <summary>Click to open/close</summary> Here is the content that is revealed when clicking the summary...</details>

Кроме этого элемент details поддерживает JavaScript-событие toggle. Так что можно изменять HTML в зависимости от того, скрыто или выводится содержимое панели.

details.addEventListener("toggle",() => { details.open? thisCoolThing(): thisOtherThing();})

Но элементы details и summary не анимируются и к ним нельзя применять переходы. Так что используем иные средства.

Шаблон разметки

Основная разметка будет выглядеть следующим образом:

<div class="container"> <button type="button" class="trigger">Show/Hide content</button> <div class="content"> All the content here </div></div>

У нас есть внешний контейнер, который оборачивает расширяемый блок. Первым элементом будет кнопка. За ней идет блок содержимого, которое будет скрываться, и выводиться при помощи пользовательских свойств CSS, переходов и JavaScript.

Базовая логика

  1. После загрузки веб-страницы измеряем высоту содержимого.
  2. Устанавливаем высоту содержимого в контейнере в виде значения пользовательского свойства CSS.
  3. Скрываем содержимое, добавив к нему атрибут aria-hidden: «true».
  4. Устанавливаем max-height в виде значения пользовательского свойства.
  5. Нажатие кнопки изменяет значение свойства aria-hidden с true на false. А также max-height содержимого с 0 на высоту, заданную в пользовательском свойстве. Далее при помощи переходов реализуем визуальный эффект.

JavaScript-код

// Получаем контейнерconst container = document.querySelector(".container");// Получаем контент:const content = document.querySelector(".content");// 1. Получаем высоту содержимого, который мы хотим показать/скрытьconst heightOfContent = content.getBoundingClientRect().height;// Получаем кнопкуconst btn = document.querySelector(".trigger");// 2. Задаем пользовательские свойства CSS с высотой контентаcontainer.style.setProperty("--containerHeight", `${heightOfContent}px`);// Когда высота заданаsetTimeout(e => { document.documentElement.classList.add("height-is-set");    3. content.setAttribute("aria-hidden", "true");}, 0);btn.addEventListener("click", function(e) {    container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true"? "false": "true");    // 5. Переключаем значение aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true"? "false": "true");})

CSS

.content {  transition: max-height 0.2s;  overflow: hidden;}.content[aria-hidden="true"] {  max-height: 0;}// 4. Задаем для высоты значение пользовательского свойства.content[aria-hidden="false"] {  max-height: var(--containerHeight, 1000px);}

Как насчет нескольких блоков?

Если на странице есть пару раскрывающихся блоков, то необходимо будет перебрать их все. Но только в том случае, если они разного размера. Для этого используйте querySelectorAll, чтобы приобрести все контейнеры и повторно задать пользовательские переменные через forEach.

setTimeout

Способ setTimeout с продолжительностью 0 до вывода контейнера используется для первоначального вывода веб-страница. Это может приобрести высоту контента.

Когда страница готова

Кроме этого можно без проблем обернуть программный код блока в возможность, которая инициализируется при загрузке страницы. К примеру:

window.addEventListener("load", initDrawers);

Мы добавим ее в ближайшее время.

Дополнительные атрибуты data для контейнера

Мы добавляем атрибут data тогда, необходимо что-то необходимо настроить, когда панель открывается / закрывается. К примеру, цвет какого-то элемента.

Значение по умолчанию для пользовательского свойства

По умолчанию для пользовательского свойства установлено значение 1000px. Оно указывается после запятой внутри значения: var(—containerHeight, 1000px). Вы можете установить иное значение.

Почему бы не использовать значение по умолчанию 10000000px?

Проблема заключается в том, что переход будет выполняться от данной высоты. Если длительность перехода установлена ​​в 1 сек., переход будет выполняться со скоростью 10000000 пикселей в секунду. Если содержимое имеет в себя высоту всего 50px, то вы получите довольно быстрый эффект открытия / закрытия.

Тернарный оператор для переключателей

Тернарный оператор будет укороченной формой if / else. В нем сначала указывается условие, которое необходимо проверить. Далее? отделяет код для выполнения, если true. После: идет программный код, который будет выполняться, если проверка ложна.

isThisTrue? doYesCode(): doNoCode();

Что происходит при изменении размера окна браузера?

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

Повышаем доступность(Accessibility)

Чтобы повысить доступность создаваемой панели, используйте атрибуты aria-expanded, aria-controls и aria-labelledby. Это даст вспомогательным технологиям лучшее представление о том, когда панели будут открыты / раскрыты. Мы добавляем aria-expanded=»false» к кнопке и aria-controls=»IDofcontent», где IDofcontent — это идентификатора контейнера с содержимым.

Далее мы используем другой тернарный оператор для переключения в JavaScript атрибута aria-expanded по клику.

Все вместе

Полная версия JavaScript-кода примера:

var containers;function initDrawers() {    // Получаем контейнер с контентом containers = document.querySelectorAll(".container");    setHeights();    wireUpTriggers();    window.addEventListener("resize", setHeights);}window.addEventListener("load", initDrawers);function setHeights() {    containers.forEach(container => {        // Получаем содержимое let content = container.querySelector(".content");        content.removeAttribute("aria-hidden");        // Высота содержимого, который необходимо скрыть/показать let heightOfContent = content.getBoundingClientRect().height;        // Задаем пользовательские свойства CSS с высотой содержимого container.style.setProperty("--containerHeight", `${heightOfContent}px`);        // Когда высота считана и задана setTimeout(e => {            container.classList.add("height-is-set");            content.setAttribute("aria-hidden", "true");        }, 0);    });}function wireUpTriggers() {    containers.forEach(container => {        // Получаем все элементы-триггеры let btn = container.querySelector(".trigger");        // Получаем содержимое let content = container.querySelector(".content");        btn.addEventListener("click",() => {            btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false"? "true": "false");            container.setAttribute(                "data-drawer-showing",                container.getAttribute("data-drawer-showing") === "true"? "false": "true");            content.setAttribute(                "aria-hidden",                content.getAttribute("aria-hidden") === "true"? "false" : "true"            );        });    });}

Вы также можете поэкспериментировать с кодом, размещенным на CodePen

Заключение

Возможно, это не самый эффективный способ реализации панелей расширения. Но для большинства ситуаций он подходит.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *