前言
使用纯css实现树形组件,整体结构需要利用到 details和 summary,它们天然地支持内容展开和收起。
特别说明:以下所有例子代码都是在react项目中进行
以下是一个简单例子:
import React from 'react';
const Unpublished = () => {
return (
<div>
<details>
<summary>这是一台机器</summary>
<p>这是一台机器展开了</p>
</details>
</div>
);
};
export default Unpublished;
效果图:
一、支持多层级嵌套
需要将details当做展开内容即可,代码如下:
import React from 'react';
const Unpublished = () => {
return (
<div>
<details>
<summary>项目1</summary>
<details>
<summary>文件夹0</summary>
</details>
<details>
<summary>文件夹1-1</summary>
<details>
<summary>文件夹1-1-2</summary>
</details>
<details>
<summary>文件夹1-1-3</summary>
</details>
</details>
</details>
</div>
);
};
export default Unpublished;
效果如下:
加上缩进样式:
details {
padding-left: 15px;
}
加上缩进样式效果:
二、画出➕和➖ 符号
默认的黑色三角太丑了,需要去掉。现代浏览器中,这个“黑色三角”其实是 ::marker生成的,而这个 ::marker是通过list-style生成,所以要去除就很简单了
旧版本浏览器需要通过专门的伪元素修改,::-webkit-details-marker和::-moz-list-bullet,现在都统一成了list-style
summary{
list-style: none;
}
也可以改变summary的display属性(默认是list-item)
summary{
display: flex;
}
这时,默认的黑色三角去除了,可以绘制➕和➖了
绘制加号(➕)和减号(➖),由于还有外围一个正方形边框,我们可以用伪元素来绘制(当然,这是在可以使用的情况下),好处是可以直接用border画边框,这比用渐变方便的多,然后加号就是两段线性渐变,如下
css代码如下:
summary::before{
content: '';
width: 14px;
height: 14px;
flex-shrink: 0;
margin-right: 8px;
border: 1px solid #999;
background: linear-gradient(#999, #999) 50%/1px 10px no-repeat,linear-gradient(#999, #999) 50%/10px 1px no-repeat;
}
现在都是加号(➕),看不出哪些是展开的,所以还需要绘制减号(➖),可以用[open]属性来判断,相较于加号(➕)而言,只需要一个线性渐变就行了,实现如下
details[open]>summary::before{
background: linear-gradient(#999, #999) 50%/10px 1px no-repeat;
}
到了这一步,其实还有一个小问题,有些是不能继续展开的,因为已经到了最底层,没有内容了,所以希望在没有展开内容的时候不显示加号(➕)或者减号(➖),这应该如何判断呢?
在没有展开内容的情况下,其实只有summary单个标签,就像这种结构
<details>
<summary>文件</summary>
<!--没有内容了-->
</details>
可以用:only-child伪类
summary:only-child::before{
display: none
}
或者用:not 伪类,直接在前面的选择器上加一层判断
summary:not(:only-child)::before{
/*排除单个summary的情况*/
}
效果如下:
三、画连接线
可以分解开来处理,一部分是垂直的,指向树的每个标题部分,所以直接绘制在summary上,还有一部分是竖直的,并且竖直部分会包含整个展开部分,因此可以把线条绘制在details上,用代码实现如下(为了区分,下面把垂直部分用红色表示)
summary{
/*水平线*/
background: linear-gradient(#999,#999) 0px 50%/20px 1px no-repeat;
}
details{
/*垂直线*/
background: linear-gradient(#999, #999) 40px 0px/1px 100% no-repeat;
}
效果如下:
树的最后一个节点,垂直线段不应该继续向下延伸了,最左侧的线也是多余的,需要去掉多余的线段
首先是最左侧的线段,其实就是最外层,也就是第一层,要去除很简单,直接选中第一层的details以及下面的summary就行了,这里可以用子选择器>来实现
.tree>details,
.tree>details>summary{
/*去除最外层的连接线*/
background: none
}
然后就是每层的最后一个子节点,如何将垂直线段去除呢?其实可以从HTML结构上入手,最后一层,其实就是最后一个details,所以将最后一个的背景尺寸改为刚好和垂直线段吻合
details:last-child{
background-size: 1px 23px;
}
现在最左侧第一层都是分开的,看着有些零散,这是因为前面这一步将所有最后一层的垂直线段都去掉了,所以需要还原这种情况,可以用子选择器>选到,如下
.tree>details:not(:last-child)>details:last-child{
background-size: 1px 100%;
}
将实线改成虚线
summary{
/*水平虚线*/
background: repeating-linear-gradient( 90deg, #999 0 1px,transparent 0px 2px) 0px 50%/20px 1px no-repeat;
}
details{
/*垂直虚线*/
background: repeating-linear-gradient( #999 0 1px,transparent 0px 2px) 40px 0px/1px 100% no-repeat;
}
最终完整css代码:
.tree summary{
outline: 0;
padding-left: 30px;
list-style: none;
background: repeating-linear-gradient( 90deg, #999 0 1px,transparent 0px 2px) 0px 50%/20px 1px no-repeat;
/* background: linear-gradient(#999,#999) 0px 50%/20px 1px no-repeat; */
}
.tree details:last-child{
background-size: 1px 23px;
}
.tree>details:not(:last-child)>details:last-child{
background-size: 1px 100%;
}
.tree details{
padding-left: 40px;
background: repeating-linear-gradient( #999 0 1px,transparent 0px 2px) 40px 0px/1px 100% no-repeat;
/* background: linear-gradient(#999, #999) 40px 0px/1px 100% no-repeat; */
}
.tree>details{
background: none;
padding-left: 0;
}
.tree>details>summary{
background: none
}
.tree summary{
display: flex;
align-items: center;
height: 46px;
font-size: 15px;
line-height: 22px;
color: rgba(0, 0, 0, 0.85);
cursor: default;
}
.tree summary::after{
content: '';
position: absolute;
left: 10px;
right: 10px;
height: 38px;
background: #EEF2FF;
border-radius: 8px;
z-index: -1;
opacity: 0;
transition: .2s;
}
.tree summary:hover::after{
opacity: 1;
}
.tree summary:not(:only-child)::before{
content: '';
width: 14px;
height: 14px;
flex-shrink: 0;
margin-right: 8px;
border: 1px solid #999;
background: linear-gradient(#999, #999) 50%/1px 10px no-repeat,linear-gradient(#999, #999) 50%/10px 1px no-repeat;
}
.tree details[open]>summary::before{
background: linear-gradient(#999, #999) 50%/10px 1px no-repeat;
}
效果图: