欢送关注微信公众号: 前端侦探

最近在我的项目中碰到了一个这样的树状构造目录,成果如下

如果用到了 Ant Design 这样的框架,那能够间接用现成的组件。如果没有用到这样的框架呢?其实纯 CSS 也是能够搞定的,上面看看如何实现的,还有很多你可能不晓得 CSS 小技巧哦~

一、details 和 summary

首先,实现这样一个交互须要利用到 details 和 summary,人造地反对内容开展和收起。这里有一个 MDN 的例子

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

成果如下

还能够反对多层嵌套,比方

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

成果如下

是不是有点乱了,还看不出层级关系?没关系,上面能够自定义款式

二、自定义树形构造

1. 缩进层级

首先须要突出层级关系,能够给每一层级加一个内边距

details{  padding-left: 10px}

全副开展的样子如下

2. 自定义三角

这个“彩色三角”太难看了,须要去掉,从开发者工具能够看到,这个“彩色三角”其实是 ::marker生成的,而这个 ::marker是通过list-style生成,如下

所以,去除这个“彩色三角”就容易了

summary{  list-style: none;}
旧版本浏览器须要通过专门的伪元素批改,::-webkit-details-marker::-moz-list-bullet ,当初都对立成了list-style

而后,能够指定自定义的三角图标,开展的款式能够通过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");}

简略丑化当前如下

3. 树形构造最深层级

下面的小三角还有点问题,比方这样一个层级

当没有开展内容时,依然能够点击切换,所以须要限度一下,这种状况下不显示小三角,示意曾经到最底层目录了,不可再开展了。

要实现这种也很简略,仔细观察 HTML 构造,当没有开展内容时,就仅存 summary元素了,是惟一的元素,提到“惟一”,能够想到:only-child,所以实现就是:

summary:not(:only-child){    background: url("xxx") 4px center no-repeat;}details[open]>summary:not(:only-child){    background-image: url("xxx");}

这样就能够很直观的看到树形目录是否曾经处于最深处了

三、自定义点击范畴

个别状况下,自定义到下面这里就能够完结了。然而,还有一点点小的体验问题,比方加个 hover 成果

.tree-item:hover{  background: aliceblue;}

很显著能够看到 层级越深,点击范畴越小。那能不能做成通栏都能够点击的呢?

这时,咱们能够借助负的margin来实现,比方给一个足够大的 padding,而后通过负的margin 归位,实现如下

.tree-item{      /**/    padding-left: 400px;    margin-left: -400px;}

这样就是通栏触发了,点击区域足够大

因为右边是足够大,曾经超出树状构造了,如果限定在树状构造类,能够通过父级超出暗藏或者滚动来解决

.tree{  overflow: auto;}

还有个问题是 hover背景遮蔽了父级的小三角,而且这种截断的形式也没法设置圆角。怎么解决呢?

能够独自应用一层伪元素,而后利用“不齐全相对定位”,什么意思呢?设置一个元素为相对定位,如果只指定一个方向,比方程度方向(left/right),那么该元素的最终体现是程度方向上的体现依赖于第一个定位的父级,垂直方向上不依赖于定位父级,依然处于默认地位

在这个例子中,咱们能够只指定程度方向上的定位属性,这样能够保障程度方向的尺寸追随最外层父级,还能够通过z-index扭转层级,不遮挡父级小三角,实现如下

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

这样就比拟完满了

还能够加上文件图标

.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");}

这样就失去了文章结尾所示的成果

残缺代码能够拜访:CSS tree(codepen.io) 或者 CSS tree (juejin.cn)

四、JS 数据渲染

大部分状况下,这类树状构造都是通过数据渲染进去的,假如有这样一段 json数据

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

这样一个能够有限嵌套的构造能够用递归来实现,这里简略实现一下

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

而后通过innerHTML赋值就行了

tree.innerHTML = gen_tree(treeData)

成果如下

五、简略总结一下

这样就通过 CSS 实现了树状构造目录,整体来说并不是很简单,次要构造是 details 和 summary,而后是一些 CSS 选择器的使用,这里简略总结一下:

  1. details 和 summary 原生反对开展收起
  2. details 和 summary 反对多层嵌套,这样就失去了繁难的树状构造
  3. details 和 summary 反对多层嵌套,这样就失去了繁难的树状构造
  4. summary 的彩色三角形是通过 list-style 生成的
  5. 开展的款式能够通过 details[open] 来定义
  6. 逐层缩进能够通过给 details 增加内边距实现
  7. 树形构造最底层能够通过 :only-child 判断
  8. 默认状况下点击区域逐层递加,体验不是很好
  9. 负的margin 和 padding 能够扩充点击区域
  10. “不齐全相对定位”能够指定一个方向上的尺寸依赖于定位父级
  11. 有限嵌套的构造能够用递归来实现

另外,兼容性方面也十分不错,支流浏览器均反对,IE 上尽管不反对 details 和 summary,然而通过 polyfill 解决,总的来说十分实用的,大能够放心使用。最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤

欢送关注微信公众号: 前端侦探