Welcome to WeChat public account: front-end detective
Recently I encountered such a tree structure directory in the project, the effect is as follows
If you use a framework like Ant Design, you can use ready-made components directly. What if such a framework is not used? In fact, pure CSS can also be done. Let's see how to achieve it. There are many CSS tricks you may not know~
1. details and summary
First, implementing such an interaction requires the use of details and summary , which naturally support content expansion and collapse. Here's an MDN example
<details>
<summary>System Requirements</summary>
<p>Requires a computer running an operating system. The computer
must have some memory and ideally some kind of long-term storage.
An input device as well as some form of output device is
recommended.</p>
</details>
The effect is as follows
It can also support multiple levels of nesting, such as
<details>
<summary>
<span class="tree-item">项目1</span>
</summary>
<details>
<summary>
<span class="tree-item">文件夹0</span>
</summary>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-1</span>
</summary>
<details>
<summary>
<span class="tree-item">文件夹1-1-2</span>
</summary>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-1-3</span>
</summary>
<details>
<summary>
<span class="tree-item">文件夹1-1-3-1</span>
</summary>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-1-3-2</span>
</summary>
</details>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-1-4</span>
</summary>
</details>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-2</span>
</summary>
<details>
<summary>
<span class="tree-item">文件夹1-2-1</span>
</summary>
</details>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-3</span>
</summary>
</details>
<details>
<summary>
<span class="tree-item">文件夹1-4</span>
</summary>
</details>
</details>
The effect is as follows
Is it a bit messy, and you can't see the hierarchical relationship? It doesn't matter, you can customize the style below
Second, the custom tree structure
1. Indent level
First of all, you need to highlight the hierarchical relationship, you can add a padding to each level
details{
padding-left: 10px
}
All unfolded as follows
2. Custom Triangle
This "black triangle" is too ugly and needs to be removed. As you can see from the developer tools, this "black triangle" is actually generated by ::marker
, and this ::marker
is generated by list-style
Generate as follows
So, it's easy to remove this "black triangle"
summary{
list-style: none;
}
The old version of the browser needs to be modified by special pseudo-elements,::-webkit-details-marker
and::-moz-list-bullet
, now they are unified intolist-style
Then, you can specify a custom triangle icon, and the expanded style can be defined by details[open]
summary{
background: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.354 2.646A.5.5 0 0 0 4.5 3v6a.5.5 0 0 0 .854.354l3-3a.5.5 0 0 0 0-.708l-3-3z' fill='%23000' fill-opacity='.45'/%3E%3C/svg%3E") 4px center no-repeat;
}
details[open]>summary{
background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.354 5.354A.5.5 0 0 0 9 4.5H3a.5.5 0 0 0-.354.854l3 3a.5.5 0 0 0 .708 0l3-3z' fill='%23000' fill-opacity='.45'/%3E%3C/svg%3E");
}
After simple beautification is as follows
3. The deepest level of the tree structure
There are still some problems with the small triangle above, such as such a level
When the content is not expanded, you can still click to switch, so you need to limit it. In this case, the small triangle is not displayed , which means that you have reached the bottom directory and cannot be expanded any more.
It is also very simple to achieve this. Carefully observe the HTML structure. When the content is not expanded, only the summary element remains, which is the only element. When it comes to "unique", you can think of :only-child
, so the implementation is :
summary:not(:only-child){
background: url("xxx") 4px center no-repeat;
}
details[open]>summary:not(:only-child){
background-image: url("xxx");
}
In this way, you can intuitively see whether the tree directory is already in the deepest
3. Customize the click range
Under normal circumstances, the customization can end here. However, there are still a little small experience problems, such as adding a hover effect
.tree-item:hover{
background: aliceblue;
}
It is obvious to see that the deeper the hierarchy, the smaller the click range . Can it be made into a column that can be clicked?
At this time, we can use a negative margin to achieve, for example, give a large enough padding, and then return to the position through a negative margin, the implementation is as follows
.tree-item{
/**/
padding-left: 400px;
margin-left: -400px;
}
In this way, the banner is triggered, and the click area is large enough
Since the left side is large enough, it has exceeded the tree structure. If it is limited to the tree structure class, it can be solved by the parent exceeding the hidden or scrolling
.tree{
overflow: auto;
}
Another problem is that the hover background covers the parent's small triangle, and this truncation method cannot set rounded corners. How to solve it?
You can use a layer of pseudo-elements alone, and then use "incomplete absolute positioning" , what does it mean? Set an element to be absolutely positioned. If only one direction is specified, such as the horizontal direction (left/right), then the final performance of the element is that the performance in the horizontal direction depends on the parent of the first positioning, and does not depend on the vertical direction. Position the parent, still at the default position .
In this example, we can only specify the positioning attribute in the horizontal direction, which can ensure that the size in the horizontal direction follows the outermost parent . You can also change the level by z-index
without blocking the parent's small triangle. The implementation is as follows
.tree{
position: relative;
}
.tree-item::after{
content: '';
position: absolute;
left: 10px;
right: 10px;/*水平方向的尺寸依赖于父级.tree*/
height: 38px;
background: #EEF2FF;
border-radius: 8px;
z-index: -1;
opacity: 0;
transition: .2s;
}
.tree-item:hover::after{
opacity: 1;
}
That's more perfect
You can also add a file icon
.tree-item::before{
content: '';
width: 20px;
height: 20px;
flex-shrink: 0;
margin-right: 8px;
background: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M.833 3.75c0-.92.746-1.667 1.667-1.667h5.417c.247 0 .481.11.64.3l1.833 2.2h7.11c.92 0 1.667.747 1.667 1.667v10c0 .92-.747 1.667-1.667 1.667h-15c-.92 0-1.667-.746-1.667-1.667V3.75zm6.693 0H2.5v4.584h15V6.25H10a.833.833 0 0 1-.64-.3l-1.834-2.2zM17.5 10h-15v6.25h15V10z' fill='%23000' fill-opacity='.45'/%3E%3C/svg%3E") center no-repeat;
}
details[open]>summary:not(:only-child)>.tree-item::before{
background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.917 2.083c.247 0 .481.11.64.3l1.833 2.2h5.443c.92 0 1.667.747 1.667 1.667v1.667h.833a.833.833 0 0 1 .817.997l-1.666 8.333a.833.833 0 0 1-.817.67H1.677a.814.814 0 0 1-.157-.013.83.83 0 0 1-.687-.82V3.75c0-.92.746-1.667 1.667-1.667h5.417zM10 6.25a.833.833 0 0 1-.64-.3l-1.834-2.2H2.5v6.564l.441-1.766a.833.833 0 0 1 .809-.631h12.083V6.25H10zm-7.266 10L4.4 9.584h12.916l-1.334 6.666H2.733z' fill='%23000' fill-opacity='.45'/%3E%3C/svg%3E");
}
This gives the effect shown at the beginning of the article
The complete code can be accessed: CSS tree (codepen.io) or CSS tree (juejin.cn)
4. JS data rendering
In most cases, this kind of tree structure is rendered by data, assuming there is such a section json
data
const treeData = [
{
"id": 2,
"name": "项目1",
"parentId": 1,
"fileCount": 14,
"children": [
{
"id": 8,
"name": "文件夹",
"parentId": 2,
"fileCount": 12,
"children": [
{
"id": 137,
"name": "sdd",
"parentId": 8,
"fileCount": 0
}
]
},
{
"id": 221,
"name": "chrome test",
"parentId": 2,
"fileCount": 2
}
]
},
{
"id": 52,
"name": "项目2",
"parentId": 1,
"fileCount": 10,
"children": [
{
"id": 54,
"name": "文件夹2-1",
"parentId": 52,
"fileCount": 10,
"children": [
{
"id": 55,
"name": "文件夹2-1-1",
"parentId": 54,
"fileCount": 0,
"children": [
{
"id": 56,
"name": "文件夹2-1-1-1",
"parentId": 55,
"fileCount": 0,
"children": [
{
"id": 57,
"name": "文件夹2-1-1-1-1",
"parentId": 56,
"fileCount": 0,
"children": [
{
"id": 58,
"name": "文件夹2-1-1-1-1-1",
"parentId": 57,
"fileCount": 0
}
]
}
]
}
]
}
]
}
]
},
{
"id": 53,
"name": "文件夹1",
"parentId": 1,
"fileCount": 12,
"children": [
{
"id": 80,
"name": "文件夹",
"parentId": 53,
"fileCount": 11
},
{
"id": 224,
"name": "文件夹2",
"parentId": 53,
"fileCount": 0
}
]
},
{
"id": 69,
"name": "项目3",
"parentId": 1,
"fileCount": 55,
"children": [
{
"id": 70,
"name": "文件夹1",
"parentId": 69,
"fileCount": 12,
"children": [
{
"id": 4,
"name": "1",
"parentId": 70,
"fileCount": 3,
"children": [
{
"id": 51,
"name": "文件夹2",
"parentId": 4,
"fileCount": 1
}
]
}
]
},
{
"id": 91,
"name": "文件夹",
"parentId": 69,
"fileCount": 10
},
{
"id": 102,
"name": "文件夹",
"parentId": 69,
"fileCount": 10
},
{
"id": 113,
"name": "文件夹",
"parentId": 69,
"fileCount": 10
},
{
"id": 121,
"name": "文件夹的副本",
"parentId": 69,
"fileCount": 10
},
{
"id": 136,
"name": "点点点",
"parentId": 69,
"fileCount": 0
},
{
"id": 140,
"name": "hewei",
"parentId": 69,
"fileCount": 3,
"children": [
{
"id": 142,
"name": "hewei02",
"parentId": 140,
"fileCount": 1
}
]
}
]
}
]
Such a structure that can be nested infinitely can be implemented by recursion, here is a simple implementation
function gen_tree(childs){
var html = ''
childs.forEach(el => {
html+=`<details>
<summary>
<span class="tree-item" title="${el.name}" data-id="${el.id}">${el.name}</span>
</summary>`
if (el.children && el.children.length) {
html += gen_tree(el.children) // 如果有chidren就继续遍历
}
html+= `</details>`
})
return html;
}
Then pass the innerHTML
assignment on the line
tree.innerHTML = gen_tree(treeData)
The effect is as follows
Five, a brief summary
In this way, the tree structure directory is realized through CSS. Overall, it is not very complicated. The main structure is details and summary, and then some CSS selectors are used. Here is a brief summary:
- details and summary native supportExpand Collapse
- details and summary support multiple levels of nesting, resulting in a simple tree structure
- details and summary support multiple levels of nesting, resulting in a simple tree structure
- The black triangle of summary is generated by list-style
- The expanded style can be defined by details[open]
- Layer-by-layer indentation can be achieved by adding padding to details
- The bottom layer of the tree structure can be judged by: only-child
- By default, the click area decreases layer by layer, and the experience is not very good
- Negative margins and padding can expand the click area
- "Not exactly absolute positioning" can specify that dimensions in one direction depend on positioning the parent
- Infinitely nested structures can be implemented with recursion
In addition, the compatibility is also very good. All mainstream browsers support it. Although details and summary are not supported on IE, they are solved by polyfill . Generally speaking, it is very practical and can be used with confidence. Finally, if you think it's good and helpful to you, please like, bookmark, and forward ❤❤❤
Welcome to WeChat public account: front-end detective
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。