Древовидное представление (сворачиваемый список) можно сделать, используя только html и css, без использования JavaScript.
Программное обеспечение для специальных возможностей будет показывать дерево в виде списков, вложенных в раскрывающиеся виджеты, а взаимодействие с клавиатурой поддерживается автоматически.
HTML
HTML разметка начинается с простых вложенных списков:
<ul>
<li>
Планеты гиганты
<ul>
<li>
Газовые
<ul>
<li>Юпитер</li>
<li>Сатурн</li>
</ul>
</li>
<li>
Ледяные
<ul>
<li>Уран</li>
<li>Нептун</li>
</ul>
</li>
</ul>
</li>
</ul>
Затем надо добавить CSS-класс самому внешнему элементу <ul>
. Внутренности каждого элемента списка, содержащего свой вложенный список, поместить внутрь элемента <details>
и добавить <summary>
. Атрибут open
e элемента <details>
будет использоваться для управления вложенными элементами. Список верхнего уровня изначально раскрыт:
<ul class="tree">
<li>
<details open>
<summary>Планеты гиганты</summary>
<ul>
<li>
<details>
<summary>Газовые</summary>
<ul>
<li>Юпитер</li>
<li>Сатурн</li>
</ul>
</details>
</li>
<li>
<details>
<summary>Ледяные</summary>
<ul>
<li>Уран</li>
<li>Нептун</li>
</ul>
</details>
</li>
</ul>
</details>
</li>
</ul>
Без CSS это будет выглядеть так:
-
Планеты гиганты
-
Газовые
- Юпитер
- Сатурн
-
Ледяные
- Уран
- Нептун
-
Пока ничто не говорит пользователю, что список можно разворачивать и открывать новые уровни, сочетание маркеров представляет запутанный пользовательский интерфейс, но это дерево уже можно раскрывать. Браузер реализует элемент <details>
, как раскрывающийся виджет, предоставляя возможность разворачивать и сворачивать вложенные списки.
Пользовательские свойства
На компоновку древовидного представления влияют два параметра: межстрочный интервал (равный высоте строки текста) и радиус маркеров. Мы начнем с создания пользовательских CSS-свойств для определения этих размеров:
.tree{
--spacing : 1.5rem;
--radius : 10px;
}
Хотя обычно используются относительные единицы для масштабирования элементов управления пользовательского интерфейса на основе размера текста, для маркеров это может привести к тому, что элементы управления будут слишком маленькими или чрезмерно большими. Поэтому здесь будет использоваться разумный фиксированный размер 10px
.
Отступы
Теперь стилизуются элементы основного и вложенных списков, чтобы подготовить место для линий и маркеров:
.tree li{
display : block;
position : relative;
padding-left : calc(2 * var(--spacing) - var(--radius) - 2px);
}
.tree ul{
margin-left : calc(var(--radius) - var(--spacing));
padding-left : 0;
}
Для селектора .tree li
CSS-свойство display: block;
удалит маркеры из элементов списка. position: relative;
установит новый контекст стека и содержимого блока, который будет использоваться для размещения линий и маркеров.
padding-left
cделает отступы для элементов списка. Отступ равен удвоенному интервалу минус радиус маркера минус ширина линии в два пикселя. В результате текст в элементе списка будет выровнен по левой стороне маркера под ним.
Для селектора .tree ul
CSS-свойство margin-left
компенсирует отступы селектора .tree li
, а padding-left
сбросит отступы, которые браузер устанавливает спискам по умолчанию.
Для древовидного представления с развернутыми вложенными списками применение этого стиля дает:
-
Планеты гиганты
-
Газовые
- Юпитер
- Сатурн
-
Ледяные
- Уран
- Нептун
-
Вертикальные линии
Вертикальные линии, которые образуют часть линий, соединяющих маркер каждого элемента списка с маркерами его вложенных списков:
.tree ul li{
border-left : 2px solid #ddd;
}
.tree ul li:last-child{
border-color : transparent;
}
Для вертикальных линий используется CSS-свойство border-left
, кроме последнего элемента в каждом списке, где рамка скрывается, поскольку линия не должна продолжаться за маркером этого элемента. Делая границу прозрачной, а не удаляя её полностью, можно избежать необходимости увеличивать отступ для компенсации выравнивания.
Применение этого стиля дает:
-
Планеты гиганты
-
Газовые
- Юпитер
- Сатурн
-
Ледяные
- Уран
- Нептун
-
Горизонтальные линии
Чтобы добавить горизонтальные линии, соединяющие вертикальные линии с маркерами каждого элемента списка, используется сгенерированный контент:
.tree ul li::before{
content : '';
display : block;
position : absolute;
top : calc(var(--spacing) / -2);
left : -2px;
width : calc(var(--spacing) + 2px);
height : calc(var(--spacing) + 1px);
border : solid #ddd;
border-width : 0 0 2px 2px;
}
Этот код создает ещё и короткие вертикальные линии, так как созданные ранее вертикальные линии не доходят до маркеров на их верхнем и нижнем концах.
Здесь создаётся блок и располагается так, чтобы начало оказалось в середине предыдущей строки текста, перекрывая вертикальную линию слева от неё.
Размеры блока должны быть на два пикселя шире интервала, чтобы он перекрывал вертикальную линию слева от него и на один пиксель выше, поскольку половина ширины горизонтальной линии находится ниже середины строки текста. Замечание: здесь используется box-sizing: border-box
, поэтому размеры включают рамку.
Применение этого стиля дает:
-
Планеты гиганты
-
Газовые
- Юпитер
- Сатурн
-
Ледяные
- Уран
- Нептун
-
HTML-элемент summary
Далее следует удалить стиль по умолчанию у HTML-элемента summary
:
.tree summary{
display : block;
cursor : pointer;
}
.tree summary::marker,
.tree summary::-webkit-details-marker{
display : none;
}
.tree summary:focus{
outline : none;
}
.tree summary:focus-visible{
outline : 1px dotted #000;
}
здесь будут удалены маркеры-стрелки и установлен курсор, чтобы обозначить возможность взаимодействовать с элементом — кликабельность.
Safari показывает индикатор фокуса вокруг summary
даже если используется мышь, а не клавиатура. Поэтому здесь сперва удаляется стиль фокуса, а затем используется псевдокласс :focus-visible
, чтобы добавить его обратно для посетителей, использующих навигацию с помощью клавиатуры.
Применение этого стиля дает:
-
Планеты гиганты
-
Газовые
- Юпитер
- Сатурн
-
Ледяные
- Уран
- Нептун
-
Маркеры
Здесь, чтобы сделать маркеры, снова используется генерируемый контент:
.tree li::after,
.tree summary::before{
content : '';
display : block;
position : absolute;
top : calc(var(--spacing) / 2 - var(--radius));
left : calc(var(--spacing) - var(--radius) - 1px);
width : calc(2 * var(--radius));
height : calc(2 * var(--radius));
border-radius : 50%;
background : #ddd;
}
Маркеры генерируются для элементов <li>
(которые не содержат вложенных списков) и для элементов <summary>
. Это позволяет элементам, содержащим вложенные списки, иметь иной стиль маркеров в зависимости от того, свернут или раскрыт вложенный список.
Здесь формируется блок, отцентрированный над пересечением горизонтальной и вертикальной линиями. Верх располагается в середине текстовой строки за вычетом радиуса. Левая сторона располагается на краю вертикальной линии минус радиус минус один пиксель, соответствующий половине ширины линии.
Применение этого стиля дает:
-
Планеты гиганты
-
Газовые
- Юпитер
- Сатурн
-
Ледяные
- Уран
- Нептун
-
Кнопки сворачивания и раскрытия списков
Наконец, осталось добавить кнопки сворачивания и раскрытия древовидных списков:
.tree summary::before{
content : '+';
z-index : 1;
background : #696;
color : #fff;
line-height : calc(2 * var(--radius) - 2px);
text-align : center;
}
.tree details[open] > summary::before{
content : '−';
}
Обратите внимание, что используется настоящий знак минус (−), а не дефис (-). Он соответствует внешнему виду знака плюс, тогда как в большинстве шрифтов дефис уже и ниже.
В зависимости от используемого шрифта, может потребоваться коррекция положения символов, например - 2px
у CSS-свойства line-height
.
Применение этого стиля приводит к готовому древовидному представлению:
Готовый код
.tree{
--spacing : 1.5rem;
--radius : 10px;
}
.tree li{
display : block;
position : relative;
padding-left : calc(2 * var(--spacing) - var(--radius) - 2px);
}
.tree ul{
margin-left : calc(var(--radius) - var(--spacing));
padding-left : 0;
}
.tree ul li{
border-left : 2px solid #ddd;
}
.tree ul li:last-child{
border-color : transparent;
}
.tree ul li::before{
content : '';
display : block;
position : absolute;
top : calc(var(--spacing) / -2);
left : -2px;
width : calc(var(--spacing) + 2px);
height : calc(var(--spacing) + 1px);
border : solid #ddd;
border-width : 0 0 2px 2px;
}
.tree summary{
display : block;
cursor : pointer;
}
.tree summary::marker,
.tree summary::-webkit-details-marker{
display : none;
}
.tree summary:focus{
outline : none;
}
.tree summary:focus-visible{
outline : 1px dotted #000;
}
.tree li::after,
.tree summary::before{
content : '';
display : block;
position : absolute;
top : calc(var(--spacing) / 2 - var(--radius));
left : calc(var(--spacing) - var(--radius) - 1px);
width : calc(2 * var(--radius));
height : calc(2 * var(--radius));
border-radius : 50%;
background : #ddd;
}
.tree summary::before{
content : '+';
z-index : 1;
background : #696;
color : #fff;
line-height : calc(2 * var(--radius) - 2px);
text-align : center;
}
.tree details[open] > summary::before{
content : '−';
}