Знакомьтесь, <details>

  • Tutorial

Я хочу рассказать о замечательном элементе <details> и показать несколько примеров его использования, от простых до безумных.


Вам знаком паттерн верстки компонента, который может менять своё состояние с видимого на скрытый:


.component {
  display:none;
}

.component.open {
  display:block;
}

toggleButton.onclick = () => component.classList.toggle('open')

А теперь забудьте. Существует элемент, который может делать это из коробки. Знакомьтесь — <details>


HTML-элемент <details> используется для раскрытия скрытой (дополнительной) информации.

Базовое применение


Прежде всего давайте посмотрим как этот элемент работает:



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


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



Чтобы изменить состояние элемента в html вам достаточно добавить атрибут open


<!-- Содержимое по-умолчанию видимо -->
<details open> ... </details>

<!-- Содержимое по-умолчанию скрыто -->
<details> ... </details>

А чтобы управлять состоянием средствами JavaScript предусмотрен специальный API:


const details = document.querySelector('details')

details.open = true  // Отобразить содержимое
details.open = false // Скрыть содержимое

Пара слов о доступности


Элемент <summary> фокусируемый. То есть передвигаясь по странице с клавиатуры вы попадёте на этот элемент. А вот содержимое может попасть в фокус только если <details> открыт, то есть фокус никогда не попадет на невидимые элементы внутри <details>.


Как правило, программы чтения с экрана хорошо справляются со стандартным использованием <details> и <summary>. Существуют некоторые вариации в объявлении в зависимости от программы и браузера. Подробнее.


Примеры использования


Далее я примерно повторю некоторые компоненты из документации bootstrap, но практически без JavaScript.


Изменяем маркер


Первое что вам может понадобится — изменить внешний вид маркера. Делается это очень просто:


summary::-webkit-details-marker {
  /* Любые стили */
}

Или вы можете скрыть стандартный маркер и реализовать собственный


/* Убираем стандартный маркер Chrome */
details summary::-webkit-details-marker {
  display: none
}
/* Убираем стандартный маркер Firefox */
details > summary {
  list-style: none;
}

/* Добавляем собственный маркер для закрытого состояния */
details summary:before {
  content: '\f0fe';
  font-family: "Font Awesome 5 free";
  margin-right: 7px;
}

/* Добавляем собственный маркер для открытого состояния */
details[open] summary:before {
  content: '\f146';
}


Collapse Component


Здесь всё просто. Базовый функционал такой же. Нужно лишь немного изменить внешний вид:



Accordion Component


Повторим предыдущий пример, немного изменим внешний вид <summary> и получим аккордеон:



Но, как видите, один элемент не закрывается когда открывается другой. Чтобы добиться этого нам понадобится пара строк JavaScript. Необходимо отслеживать появление атрибута open на одном из элементов <details> и удалять его у остальных:



Popover Component


Эта реализация очень похожа на Collapse Component, с той разницей что содержимое <details> имеет абсолютное позиционирование и перекрывает контент.




В своей основе это тот же Popover Component. Отличается лишь внешний вид.



Тот же пример, только с отдельной кнопкой



Но у Dropdown Component есть ещё одно важное отличие: по клику за его пределами он должен скрываться. Чтобы реализовать это снова понадобится написать пару строк JavaScript.


// По клику на тело документа
document.body.onclick = () => {
  // Найти все открытые <details>
  document.body.querySelectorAll('details.dropdown[open]')
    // И закрыть каждый из них
    .forEach(e => e.open = false)
}


И напоследок пример модального окна.



Вообще <details> не лучший выбор для реализации этого компонента. Существует куда более подходящий элемент — <dialog>, но у него весьма плохая поддержка браузерами.


Ссылки


Can I Use Details & Summary elements
MDN details element
W3C details element


UPD.
Решил добавить ещё один пример использования <details> — многоуровневая навигация. Ещё раз хочу обратить ваше внимание на то, что пример работает без какого либо JavaScript. И он намного более инклюзивный чем традиционная верстка на <div>.


Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 31

    +15
    Дьявол кроется в details
      0
      Edge со следующей версии должен поддерживать, а для IE и Opera Mini есть полифил, так что не все так плохо.
      +6
      :-webkit-details-marker

      Очень кроссбраузерно.


      И как анимировать?

        +2
        С анимацией есть свои тонкости. Но в общих чертах вы можете анимировать появление вложенного элемента. Например так:
        details[open] .dropdown-menu {
          animation: slide .3s ease-in-out;
        }
        
        @keyframes slide {
          0%    {opacity: 0; transform:  translate(0, -10px); }
          100%  {opacity: 1; transform:  translate(0, 0);}
        }
        



          +1
          Очень кроссбраузерно

          В лисе (70.0.1) работает.
          В сафари тоже.

            +3
            В лисе (70.0.1) работает.

            -webkit? Не верю :) И у меня не работает. Как подсказывает MDN, правильный способ скрыть дефолтную стрелку:


            details > summary {
              list-style: none;
            }

            А вот в Chrome-like придётся делать так, как и говорится в статье:


            details > summary::-webkit-details-marker {
              display: none;
            }
              0
              Спасибо. Подправил пример
                +1

                Автор подлампичить успел, пруфов не будет.

                  +1

                  Большая куча -webkit-штук в лисе-таки работает. Например, -webkit-background-clip: text в связке с -webkit-text-fill-color: transparent. Но этому конкретному псевдоэлементу (как и его товарищам по оформлению нативных контролов) не так повезло, да.

                    0

                    Ну так одно дело прозрачно транслировать css-свойство в нативный вид или в префикс -moz, что можно реализовать весьма просто и дёшево, а другое дело — поддерживать чужие псевдоэлементы, которые прямо зависят от деталей реализации.

                      0

                      Префикс -moz фактически остался только у тех самых деталей реализации (для новых свойств все браузеры дружно отказались от префиксов в пользу флагов), но да, по-видимому всё решает баланс востребованности фичи и легкости реализации. Для стилизации скроллбаров ребята из Мозиллы не поленились написать с нуля целую новую спеку, лишь бы не перенимать ::-webkit-scrollbar и т.п.

                0

                Справедливости ради, префикс -webkit- во многих случаях давно перестал быть "вендорским", так что его наличие – еще не показатель некроссбраузерности (нестандартности – пожалуй, да). А с нативными контролами по части CSS-оформления вообще традиционно всё очень плохо, и без браузероспецифичных костылей можно будет обойтись очень нескоро...

                0
                Знакомьтесь, <details>

                Уже достаточно давно знакомы и активно и плодотворно сотрудничаем :)
                  0

                  Вот недавно статья была на эту же тему: https://m.habr.com/ru/post/465623/

                    +1
                    костыли они такие костыли
                      +1
                      Спасибо Вам, добрый человек, очень помогло! Я до сих пор очень многое из описанного делал по колхозному с помощью бустрапа((
                        –1
                        Делал как-то дерево на details. Все работает нормально, но код невалидный — они не могут быть вложенными.
                          0
                          Ради любопытства зашел на validator.w3.org. Сделал 4 уровня вложенности. И тот не выдал ни одной ошибки.
                            0
                            Element details not allowed as child of element summary in this context.

                            Видимо, ваше дерево было проще моего.
                              +2

                              Видимо, вы пытались сделать


                              <details>
                                <summary>
                                  Level 1
                                  <details>
                                      <summary>
                                        Level 2
                                        ...
                                      </summary>
                                  </details>
                                </summary>
                              </details>

                              Тогда как нужно


                              <details>
                                <summary>Level 1</summary>
                                <details>
                                  <summary>Level 2</summary>
                                  ...
                                </details>
                              </details>

                              Обратите внимание на текст ошибки: "Элемент details не разрешается в качестве дочернего для элемента summary".

                          0
                          Где же вы были месяц назад!
                          Спасибо большое за статью в любом случае)
                            0

                            И чем плох метод с переключением классов?

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


                              • Реализация не всегда настолько хороша. Скажем, вы задумывались о том, что контрол, по клику на который должен переключиться класс должен реагировать не только на клик мышкой, тап по экрану но и на клавиатуру? Задумывались о том, что он должен быть доступен при проходе табуляцией? Конечно, если для этого использовался <button> то всё более менее хорошо, но ведь очень часто используют какой-либо другой элемент, который только выглядит как кнопка, скажем


                                <span class="btn">

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


                              • Семантика и контекст. Если вы воспринимаете страницу не визуально (как это делают многие люди и абсолютно все боты и программы) то для вас крайне важно понимать контекст. Для вас <div> или <span> не говорят ничего. Даже <button> — это просто кнопка без привязки к чему либо. <details> же указывает на то, что это интерактивный элемент, у которого есть оглавление и при желании можно раскрыть детали. Конечно есть aria-атрибуты, но далеко не многие разработчики об этом задумываются. Даже мои примеры сделаны не лучшим образом с точки зрения семантики. На эту тему советую посмотреть доклад "Семантика для циников, Вадим Макеев"


                                0
                                Единственный плюс, который я увидел и он мне он лично нравится это взаимодействие со страницей при нажатии на таб или просмотра страницы с электронной книги.

                                Производительность хуже.

                                Давайте конкретнее. Производительность хуже если одновременно открывать и закрывать 1 000 000 html элементов? на обычных сайтах это будет незаметно.

                                То есть лишние стили, лишние скрипты лишняя работа для браузера.

                                То же самое я вижу и в вашем примере. Лишние стили чтобы взаимодействовать с иконкой.

                                details[open] summary:before {
                                  content: '\f146';
                                }
                                

                                и опять же чем тогда данный CSS селектор будет отличатся от .details.open? :)

                                Задумывались о том, что он должен быть доступен при проходе табуляцией?

                                Семантика и контекст.

                                В обоих случая согласен. Есть важна семантика то данный тег отлично вписывается.
                                  0

                                  Говоря про производительность я говорю вот про что.
                                  Возьмём для примера простой код:


                                  <div class="details">
                                    <span class="summary">Details</span>
                                    <p class="hidden">Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio temporibus aliquam at tempora repellat consectetur eos voluptate facilis tempore obcaecati laborum animi sunt, rem quis. Dolore, ipsa. Tempora, soluta quaerat!</p>
                                  </div>

                                  document.querySelector('.summary').onclick = () => 
                                    document.querySelector('.details > p').classList.toggle('hidden')

                                  Браузер должен:


                                  1. Скачать несколько байт JavaScript кода.
                                  2. Спарсить его.
                                  3. Скомпилировать.
                                  4. Найти элемент .summary в дом дереве.
                                  5. Повесить на него обработчик событий.
                                  6. Держать его в памяти.
                                  7. По клику найти в дом дереве целевой элемент .details.
                                  8. Изменить в нем набор классов.
                                  9. Пересчитать для этого элемента стили.
                                  10. Пересчитать стили для всей страницы.
                                  11. Заново отрисовать страницу.


                                  В случае использования встроенных элементов, браузеру нужно всего-то


                                  1. Пересчитать для этого элемента стили.
                                  2. Пересчитать стили для всей страницы.
                                  3. Заново отрисовать страницу.

                                  Я понимаю, что разница мизерная. Но Великое берёт начало с малого.

                                    0
                                    Еще добавлю свои пять копеек. Допустим, на вашем ресурсе пользователям доступна кастомная верстка с возможностью добавлять «спойлеры». Вы предоставляете контент по API в виде html, чтобы ускорить отображение (пусть даже с какой-то конфигурацией). Теперь у вас есть выбор — либо использовать checkbox для того чтобы открывать/закрывать содержимое, либо вешать listener на документ, чтобы подгружаемые по xhr спойлеры сразу работали. Притом если по какой-то причине исполнение js прервется до навешивания этого слушателя, то спойлеры вообще не будут работать.

                                    Ну и, понятное дело, что если вы используете какой-нибудь react или vue — в вашу сторону за такие «навешивания» будут коситься.

                                    Нативное решение для достаточно популярного элемента интерфейса — это всегда лучше чем пусть и проверенные временем костыли.
                              0
                              Спасибо за обозрение тега. (по сути если очень надо без JS, этого же эффекта раскрывашки можно добиться через input type=«checkbox» + label, но так действительно проще, ) И у меня вопрос. А как держать внутреннее содержимое summary по дефолту открытым?
                                0

                                Как я и написал — просто добавить атрибут open


                                <!-- Содержимое по-умолчанию видимо -->
                                <details open> ... </details>

                                При переключении этот атрибут добавляется/удаляется.

                                0
                                Благодарю! Очень полезный элемент!
                                  0
                                  Интересно и очень круто. Если не пользоваться bs конечно
                                    0

                                    Вы можете делать так как я в своих примерах: брать от бутстрапа только стили, а JS добавлять собственный и только там где нужно.

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

                                  Самое читаемое