沒有用到任何 JavaScript,非常巧妙
這是如何實現(xiàn)的呢?一起來看看吧~
一、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>
直接就實現(xiàn)了展開和收起

還可以支持多層嵌套,只需要將details
當做展開的內容就行了,如下
<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>
這樣就得到了一個簡單的樹狀結構
看著還不像?那是因為現(xiàn)在還沒有縮進,可以這樣
details{
padding-left: 10px
}
簡單調整一下間距后得到這樣的效果,是不是要清晰很多?
二、繪制加號和減號
首先,默認的黑色三角太丑了,需要去掉。現(xiàn)代瀏覽器中,這個“黑色三角”其實是 ::marker
生成的,而這個 ::marker
是通過list-style
生成,所以要去除就很簡單了
舊版本瀏覽器需要通過專門的偽元素修改,::-webkit-details-marker
和::-moz-list-bullet
,現(xiàn)在都統(tǒng)一成了list-style
summary{
list-style: none;
}
當然,也可以改變summary
的display
屬性(默認是list-item
)
summary{
display: flex;
}
這樣,默認的三角就去除了
然后,繪制加號(?)和減號(?),由于還有外圍一個正方形邊框,我們可以用偽元素來繪制(當然,這是在可以使用的情況下),好處是可以直接用border
畫邊框,這比用漸變方便的多,然后加號就是兩段線性漸變,如下

用代碼實現(xiàn)就是
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;
}
調整一下間距,效果如下?
現(xiàn)在都是加號(?),看不出哪些是展開的,所以還需要繪制減號(?),可以用[open]
屬性來判斷,相較于加號(?)而言,只需要一個線性漸變就行了,實現(xiàn)如下
details[open]>summary::before{
background: linear-gradient(#999, #999) 50%/10px 1px no-repeat;
}
現(xiàn)在就可以區(qū)分哪些是展開,哪些是折疊的了
到了這一步,其實還有一個小問題,有些是不能繼續(xù)展開的,因為已經(jīng)到了最底層,沒有內容了,所以希望在沒有展開內容的時候不顯示加號(?)或者減號(?),這應該如何判斷呢?
其實很簡單,在沒有展開內容的情況下,其實只有summary
單個標簽,就像這種結構
<details>
<summary>文件</summary>
</details>
提到單個標簽,可以想到:only-child
偽類,所以可以這樣重置一下
summary:only-child::before{
display: none
}
還有另外一種做法,那就是借助:not
偽類,直接在前面的選擇器上加一層判斷
summary:not(:only-child)::before{
}
這樣會更加優(yōu)雅~效果如下
這樣就能輕易的看出哪些是不能展開的了
三、繪制連接線
最后就是繪制連接線,也是 CSS 最靈活的、最有趣的一部分。
先從繪制實線開始,這樣比較容易。
直接繪制可能有些難度,我們可以分解開來,一部分是垂直的,指向樹的每個標題部分,所以直接繪制在summary
上,還有一部分是豎直的,并且豎直部分會包含整個展開部分,因此可以把線條繪制在details
上,用代碼實現(xiàn)如下(為了區(qū)分,下面把垂直部分用紅色表示)
summary{
background: linear-gradient(#999,#999) 0px 50%/20px 1px no-repeat;
}
details{
background: linear-gradient(#999, #999) 40px 0px/1px 100% no-repeat;
}
效果如下

看著好像有些凌亂?確實有很多線是多余的,比如樹的最后一個節(jié)點,垂直線段不應該繼續(xù)向下延伸了,最左側的線也是多余的,下面是示意圖,我們其實想要右邊那樣的效果

首先是最左側的線段,其實就是最外層,也就是第一層,要去除很簡單,直接選中第一層的details
以及下面的summary
就行了,這里可以用子選擇器>
來實現(xiàn)
.tree>details,
.tree>details>summary{
background: none
}
效果如下

然后就是每層的最后一個子節(jié)點,如何將垂直線段去除呢?其實可以從HTML
結構上入手,最后一層,其實就是最后一個details
,所以將最后一個的背景尺寸改為剛好和垂直線段吻合
details:last-child{
background-size: 1px 23px;
}
為了區(qū)分,下面將這一部分用藍色表示

還有一個小優(yōu)化,現(xiàn)在最左側第一層都是分開的,看著有些零散,這是因為前面這一步將所有最后一層的垂直線段都去掉了,所以需要還原這種情況,可以用子選擇器>
選到,如下
.tree>details:not(:last-child)>details:last-child{
background-size: 1px 100%;
}
為了區(qū)分,下面將這一部分用紫色表示

實線畫出來了,虛線還遠嗎?同樣也可以用漸變實現(xiàn),只不過需要用repeating-linear-gradient
,因為虛線其實是不斷重復的從實色到透明的漸變,示意如下

用代碼實現(xiàn)就是
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;
}
這樣就實現(xiàn)了文章開頭效果了
下面是完整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;
}
.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;
}
.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;
}
你也可以查看以下任意鏈接:
四、總結一下
以上就是本文的全部內容了,可以看到全部由 CSS 繪制而成,沒有用到任何圖片,是不是很簡單呢?下面總結一下實現(xiàn)要點
details
和 summary
原生支持展開收起details
和 summary
支持多層嵌套,這樣就得到了簡易的樹狀結構- 逐層縮進可以通過給
details
添加內邊距實現(xiàn) summary
的黑色三角形是通過 list-style
生成的,可以更改 display
屬性去除- 利用偽元素可以輕易實現(xiàn)
border
邊框,這比用漸變方便的多 - 加號其實是兩段線性漸變疊加而成,減號一段漸變就夠了
- 連接線可以分成兩段,垂直線段繪制在 details 上,水平線段繪制在
summary
上 - 多余的線段可以通過
:last-child
和子選擇器>
去除 - 虛線其實是不斷重復的從實色到透明的漸變,可以用
repeating-linear-gradient
繪制
相比于現(xiàn)有的組件庫,原生實現(xiàn)最大的好處就是靈活性,合理運用選擇器,各式各樣的設計都能輕易實現(xiàn),組件庫可兼顧不了這么多。另外,兼容性方面也非常不錯,主流瀏覽器均支持
?轉自https://juejin.cn/post/7251501860321411130
該文章在 2025/9/10 14:29:18 編輯過