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

Аккордеон и faq

Создать аккордеон, faq, спойлер и подобное, можно при помощи Div и JavaScript.
Но лучше: Details и Summary

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

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

Минус:

  1. Старые браузеры не знают таких тегов и не будут скрывать информацию.

HTML:

<details>
    <summary>Покажи-скрой меня</summary>
    <p>Скандинавская мифология — мифология древних скандинавов</p>
</details>
<details open>
    <summary>Покажи-скрой меня 2</summary>
    <p>Основным источником сведений о ней являются тексты поэтической </p>
</details>
<details>
    <summary>Покажи-скрой меня 3</summary>
    <p>Скандинавская мифология — мифология древних скандинавов</p>
</details>

Простой пример Details/Summary

Демонстрация:


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

Спойлер от Хабра

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

  1. Старые браузеры его не видят.
  2. Вебкит баузеры не позволяют менять символ маркера.

По этой причине, дефолтный маркер надо спрятать и создать свой.


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

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

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
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{
  display:block;
  margin-bottom: .5rem;
}

Дефолтным маркерам делаем display:none и показываем альтернативный при помощи summary:before {content: "+";}

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

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

Демонстрация:


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

Details/Summary - текстовый маркер справа + простейшая анимация текста и маркера

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
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%    {opacity: 0;}
  100%  {opacity: 1;}
}
details{
  display:block;
  margin-bottom: .5rem;
}

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

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

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

Демонстрация:


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

Details/Summary - svg маркер + анимация поворота

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
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%    {opacity: 0;}
  100%  {opacity: 1;}
}
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{
  display:block;
  margin-bottom: .5rem;
}

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

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

Так же нужно у summary поставить отступ padding-left: 1.5em, чтоб текст и иконка не накладывались друг на друга.

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

Демонстрация:


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

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

Details/Summary - svg маркер справа + анимация поворота

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
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%    {opacity: 0;}
  100%  {opacity: 1;}
}
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{
  display:block;
  margin-bottom: .5rem;
}

Демонстрация:


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

Аккордион - иконка слева, фон, тени, эффекты

CSS:

body{background: #edf2f7;}
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{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
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%    {opacity: 0;}
  100%  {opacity: 1;}
}
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);
}

Демонстрация:


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

Аккордион - иконка справа + эффект зеркального поворота стрелки

CSS:

body{background: #edf2f7;}
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{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
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%    {opacity: 0;}
  100%  {opacity: 1;}
}
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);
}

Демонстрация:


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

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

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

JavaScript:

var details = document.querySelectorAll("details");
for(i=0;i<details.length;i++) {
  details[i].addEventListener("toggle", accordion);
}
function accordion(event) {
  if (!event.target.open) return;
    var details = event.target.parentNode.children;
    for(i=0;i<details.length;i++) {
      if (details[i].tagName != "DETAILS" || 
         !details[i].hasAttribute('open') || 
         event.target == details[i]) {
         continue;
      }
      details[i].removeAttribute("open");
    }
}

Демонстрация:


Таблица поддержки Details/Summary браузерами.

С уважением, создатель конструктора лэндингов для фрилансеров CMS cPortfolio
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

    +2

    Круто. В идеале хочу совсем уйти от js на простеньких сайтах.

      0

      Для старых браузеров можно собрать аналог details на css используя checkbox, label, div и селектор :checked.


      Аналогично если не хочется Javascript использовать для автозакрывающихся details можно собрать аналог на radio, label, div и том же селекторе :checked.

        0
        Да, вы правы, ваш вариант — это что-то среднее между details и между кодом на JavaScript! И в ряде случаев :checked это идеальный вариант!)
        Но семантика при этом опускается…
        0
        Супер, спасибо за статью!
          0
          А я и не знал про тэги такие, спасибо :)
            0

            Спасибо за статью, но некоторые примеры неверны, хотя в Codepen все верно


            details[open] > summary:before {
              transform: rotateZ(90deg);
            }
            

            а должно быть


            details[open] > summary:before {
              transform: scale(1, -1);
            }
              0
              Спасибо! Поправил!)
              –2
              border-radius: 8px;

              Вот с этого-то момента Вы всё и испоганили.

              В остальном спасибо за статью, открыл для себя новые возможности HTML5.
                +1
                И вам спасибо! Но поясните, чем вам не угодил border-radius? или вам не понравился px? надо было в %?;)
                  –5
                  Богомерзкие скругления везде. Это выглядит игрушечно и нетехнологично. Да, субъективно, но и комментарии мы пишем от своего лица, так что имеем право.
                    +1
                    понятно) это действительно субъективно, кому-то нравится, а кого-то раздражает…
                      0
                      А что вы понимаете под словом «нетехнологично»?
                      0
                      Лично мне не нравятся px, особенно там, где этот размер не привязан, скажем, к изображению в px.
                    0
                    А чтобы раскрытие было плавно можно сделать?
                      0
                      Если вы про плавное изменение высоты, то это тянет на отдельный пост на хабре)

                      Способов много, при этом идеального нет, у каждого свои плюсы и минусы…

                      Один из самых простых и часто используемых, использовать max-height.

                      CSS:
                      details summary ~ div{
                        overflow: hidden;
                      }
                      details[open] summary ~ div{ 
                        animation: sweep 2s ease-in-out;
                      }
                      @keyframes sweep {
                        0%    {max-height:0;}
                        100%  {max-height:2000px;}
                      }
                      

                      HTML:
                      <details>
                        <summary>Покажи-скрой меня</summary>
                        <div>Скандинавская мифология — мифология древних скандинавов.</div>
                      </details>
                      
                        0
                        Почему так, а не через transition?
                        details summary ~ div{
                          max-height: 0;
                          overflow: hidden;
                          transition: all 2s ease-in-out;
                        }
                        details[open] summary ~ div{ 
                          max-height: 2000px;
                        }
                        

                        Вроде попроще выглядит. Или там какие-то подводные камни?
                          0
                          1. Если использовать animation, то max-height ставится и снимается во время анимации. В вашем случае, после окончания анимации max-height остается, а текста может быть больше чем 2000px.
                          2. Ваш пример не работает.

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

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