24
头图
Welcome to WeChat public account: front-end detective

Recently I encountered such a tree structure directory in the project, the effect is as follows

Kapture 2022-04-10 at 17.48.33

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

Kapture 2022-04-10 at 18.09.18

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

Kapture 2022-04-10 at 18.24.16

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

image-20220410183028214

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

image-20220410183611323

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 into list-style

image-20220410184100481

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

image-20220410184519528

3. The deepest level of the tree structure

There are still some problems with the small triangle above, such as such a level

image-20220410185008008

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

image-20220410191039926

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;
}

Kapture 2022-04-10 at 19.22.22

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

Kapture 2022-04-10 at 19.38.06

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;
}

Kapture 2022-04-10 at 19.46.15

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

Kapture 2022-04-10 at 20.02.48

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

Kapture 2022-04-10 at 17.48.33

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

Kapture 2022-04-10 at 20.23.05

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:

  1. details and summary native supportExpand Collapse
  2. details and summary support multiple levels of nesting, resulting in a simple tree structure
  3. details and summary support multiple levels of nesting, resulting in a simple tree structure
  4. The black triangle of summary is generated by list-style
  5. The expanded style can be defined by details[open]
  6. Layer-by-layer indentation can be achieved by adding padding to details
  7. The bottom layer of the tree structure can be judged by: only-child
  8. By default, the click area decreases layer by layer, and the experience is not very good
  9. Negative margins and padding can expand the click area
  10. "Not exactly absolute positioning" can specify that dimensions in one direction depend on positioning the parent
  11. 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

XboxYan
18.1k 声望14.1k 粉丝