Html с выпадающими блоками

Создаем блоки раскрывающегося текста на HTML, CSS и JavaScript

В этой статье описывается, как без использования сторонних библиотек с помощью 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 элемента.

Читайте также:  Генератор svg для html

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

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

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

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

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

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

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

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

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

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

 
All the content here

У нас есть внешний контейнер, который оборачивает расширяемый блок. Первым элементом является кнопка. За ней идет блок содержимого, которое будет скрываться, и отображаться с помощью пользовательских свойств 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", `$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»]

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

Если на странице есть несколько раскрывающихся блоков, то нужно будет перебрать их все. Но только в том случае, если они разного размера. Для этого используйте 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", `$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

Заключение

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

Вадим Дворников автор-переводчик статьи « Make Your Own Expanding And Contracting Content Panels »

Пожалуйста, опубликуйте свои отзывы по текущей теме статьи. За комментарии, лайки, отклики, дизлайки, подписки низкий вам поклон!

Источник

Аккордеон, faq, спойлер и другие раскрывающиеся виджеты

Поддерживается всеми современными браузерами и это семантически правильно оформленный код, при использовании которого будут плюсы:

  1. Людям с ограниченными возможностями проще будет пользоваться вашим сайтом! Их софт (скринридеры и подобное) прекрасно понимает html5 теги и будет правильно обрабатывать их и правильно информировать людей о содержимом.
  2. Улучшится связанность текста, и поисковики смогут более качественно индексировать сайт, так как будут лучше понимать, как связаны между собой видимый и скрытый текст.
  3. Будет доступно управление элементами с клавиатуры и других устройств.
  4. Уменьшается количество javascript кода, который нужно подгружать, что увеличивает скорость загрузки страницы, скорость обработки и корректность.
  5. Улучшаются показатели в Lighthouse, Google PageSpeed и других подобных инструментах.
  6. Работает при выключенном javascript.
 
Покажи-скрой меня

Скандинавская мифология — мифология древних скандинавов

Покажи-скрой меня 2

Основным источником сведений о ней являются тексты поэтической

Покажи-скрой меня 3

Скандинавская мифология — мифология древних скандинавов

С одной стороны выглядит не очень красиво, с другой стороны нейтрально и легко может вписаться во многие дизайны. Кстати, дефолтный вид тега Details очень похож на спойлер от хабра, только нужно чуть перекрасить, сделать подчеркивание и получим семантически правильный, без javascript и дивов, хабровский спойлер.

К сожалению, у дефолтного маркера есть два недостатка:

Рассмотрим первый пример Details/Summary с измененным текстовым маркером:

summary::-webkit-details-marker summary::-moz-list-bullet summary::marker summary < display:inline-block; padding: .3em .5em .3em .4em; font-size:1.4em; cursor: pointer; >summary:before < content: "+"; margin-right: .3em; >details[open] > summary:before < content: "–"; >summary ~ * < padding:0 1em 0 1em; >summary:focus < outline:0; box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3); >details

summary:focus — обводка при помощи box-shadow, это нужно для клавиатуры, чтоб видно было активный элемент и можно было перемещаться клавишей таб и открывать и закрывать при помощи пробела.

Для тега summary я поставил display:inline-block — это чтоб он не растягивался на всю ширину и были кликабельными только слова, а не вся строка.

Текстовый маркер справа + простейшая анимация текста и маркера:

summary::-webkit-details-marker summary::-moz-list-bullet summary::marker summary < display:inline-block; padding: .3em .5em .3em .4em; font-size:1.4em; cursor: pointer; >summary:after < content: "+"; margin-left: .3em; display: inline-block; transition: transform .5s; >details[open] > summary:after < transform: scale(1,-1); >summary ~ * < padding:0 1em 0 1em; >summary:focus < outline:0; box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3); >details[open] summary ~ * < animation: sweep .5s ease-in-out; >@keyframes sweep < 0% 100% > details

В новом примере я использую для маркера summary:after вместо summary:before, для того чтоб он отображался справа.

Анимация маркера при помощи transform: scale(1,-1);

Всем элементам, которые находится после summary, ставлю анимацию плавного появления при помощи animation: sweep .5s ease-in-out;

Svg маркер + анимация поворота:

summary::-webkit-details-marker summary::-moz-list-bullet summary::marker summary < display:inline-block; padding: .3em .6em .3em 1.5em; font-size:1.4em; cursor: pointer; position: relative; >summary:before < left: .3em; top: .4em; color: transparent; background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41OSAxNi4zNGw0LjU4LTQuNTktNC41OC00LjU5TDEwIDUuNzVsNiA2LTYgNnoiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em; width: 1em; height: 1em; content: ""; position: absolute; transition: transform .5s; >details[open] > summary:before < transform: rotateZ(90deg); >summary ~ * < padding:0 1em 0 1em; >details[open] summary ~ * < animation: sweep .5s ease-in-out; >@keyframes sweep < 0% 100% > summary:focus < outline:0; box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3); >details

Summary:before пришлось серьезно переделать:

  1. Поставить position: absolute; left: .3em; top: .4em; width: 1em; height: 1em;
  2. Текстовому маркеру надо обязательно поставить color: transparent; иначе он будет виден.
  3. Картинку вешаем при помощи background.

Ну и добавляем transform: rotateZ(90deg) для красивого поворота стрелки.

Если нам нужна svg иконка справа, то нужно поменять summary:before и вместо left поставить right.

Для summary поставить padding-right: 1.5em;

summary::-webkit-details-marker summary::-moz-list-bullet summary::marker summary < display:inline-block; padding: .3em 1.5em .3em .6em; font-size:1.4em; cursor: pointer; position: relative; >summary:before < right: .3em; top: .4em; color: transparent; background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41OSAxNi4zNGw0LjU4LTQuNTktNC41OC00LjU5TDEwIDUuNzVsNiA2LTYgNnoiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em; width: 1em; height: 1em; content: ""; position: absolute; transition: transform .5s; >details[open] > summary:before < transform: rotateZ(90deg); >summary ~ * < padding:0 1em 0 1em; >details[open] summary ~ * < animation: sweep .5s ease-in-out; >@keyframes sweep < 0% 100% > summary:focus < outline:0; box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3); >details

Давайте теперь сделаем один из наиболее распространенных примеров создания аккордиона, где будет иконка слева, фон, тени, эффекты:

body details < display:block; background: #fff; width:400px; box-shadow: 0 10px 15px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); border-radius: 8px; overflow:hidden; margin-bottom: 1.5rem; >summary::-webkit-details-marker summary::-moz-list-bullet summary::marker summary < display:block; padding: .3em .3em .3em 1.4em; font-size:1.4em; cursor: pointer; position: relative; border-bottom: 1px solid #e2e8f0; >summary:before < top: .4em; left: .3em; color: transparent; background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41OSAxNi4zNGw0LjU4LTQuNTktNC41OC00LjU5TDEwIDUuNzVsNiA2LTYgNnoiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em; width: 1em; height: 1em; content: ""; position: absolute; transition: transform .5s; >details[open] > summary:before < transform: rotateZ(90deg); >summary ~ * < padding: 0 2em 10px 2em; >details[open] summary ~ * < animation: sweep .5s ease-in-out; >@keyframes sweep < 0% 100% > summary:focus

Svg маркер справа + эффект зеркального поворота стрелки:

body details < display:block; background: #fff; width:400px; box-shadow: 0 10px 15px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); border-radius: 8px; overflow:hidden; margin-bottom: 1.5rem; >summary::-webkit-details-marker summary::-moz-list-bullet summary::marker summary < display:block; padding: .3em 1em .3em .9em; border-bottom: 1px solid #e2e8f0; font-size:1.4em; cursor: pointer; position: relative; >summary:before < top: .4em; right: .3em; color: transparent; background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTYuNTkgOC41OUwxMiAxMy4xNyA3LjQxIDguNTkgNiAxMGw2IDYgNi02eiIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em; width: 1em; height: 1em; content: ""; position: absolute; transition: transform .5s; >details[open] > summary:before < transform: scale(1,-1); >summary ~ * < padding: 0 1em 10px 1.4em; >details[open] summary ~ * < animation: sweep .5s ease-in-out; >@keyframes sweep < 0% 100% > summary:focus

Теперь вы можете создавать красивые аккордионы, спойлеры и faq, без JavaScript, на чистом HTML5 и CSS.

Прежде чем убирать outline, 100 раз подумайте, чем вы можете его заменить, чтоб человек мог видеть фокус и мог перемещаться с клавиатуры или других устройств.

Если вам нужно, чтоб при открытии одного спойлера, закрывались остальные, то придется применить javascript, ниже привожу пример простого JS кода, который решит эту проблему.

var details = document.querySelectorAll("details"); for(i=0;i function accordion(event) < if (!event.target.open) return; var details = event.target.parentNode.children; for(i=0;idetails[i].removeAttribute("open"); > > 

С уважением, создатель конструктора лэндингов для фрилансеров CMS cPortfolio

Источник

Оцените статью