純 CSS 實(shí)現(xiàn)帶連接線的樹形組件
之前在這篇文章(CSS 實(shí)現(xiàn)樹狀結(jié)構(gòu)目錄[1])中實(shí)現(xiàn)了一個(gè)樹狀結(jié)構(gòu),效果是這樣的
Kapture 2022-04-10 at 17.48.33整個(gè)實(shí)現(xiàn)沒有用到任何 JavaScript,非常巧妙,有興趣可以回顧一下。
不過有時(shí)候還需要那種帶連接線的樣式,這樣看起來層級(jí)會(huì)更清晰,就像這樣
Kapture 2023-06-28 at 19.47.32這是如何實(shí)現(xiàn)的呢?一起來看看吧~
一、details 和 summary
簡(jiǎn)單回顧一下,整體結(jié)構(gòu)需要利用到 details[2] 和 summary[3],天然地支持內(nèi)容展開和收起。這里有一個(gè) 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>
直接就實(shí)現(xiàn)了展開和收起
Kapture 2022-04-10 at 18.09.18還可以支持多層嵌套,只需要將details當(dāng)做展開的內(nèi)容就行了,如下
<details>
??<summary>項(xiàng)目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>
這樣就得到了一個(gè)簡(jiǎn)單的樹狀結(jié)構(gòu)
Kapture 2022-04-10 at 18.24.16看著還不像?那是因?yàn)楝F(xiàn)在還沒有縮進(jìn),可以這樣
details{
??padding-left:?10px
}
簡(jiǎn)單調(diào)整一下間距后得到這樣的效果,是不是要清晰很多?
image-20230629161203001二、繪制加號(hào)和減號(hào)
首先,默認(rèn)的黑色三角太丑了,需要去掉。現(xiàn)代瀏覽器中,這個(gè)“黑色三角”其實(shí)是 ::marker生成的,而這個(gè) ::marker是通過list-style生成,所以要去除就很簡(jiǎn)單了
舊版本瀏覽器需要通過專門的偽元素修改,
::-webkit-details-marker和::-moz-list-bullet,現(xiàn)在都統(tǒng)一成了list-style
summary{
??list-style:?none;
}
當(dāng)然,也可以改變summary的display屬性(默認(rèn)是list-item)
summary{
??display:?flex;
}
這樣,默認(rèn)的三角就去除了
image-20230629162539762然后,繪制加號(hào)(?)和減號(hào)(?),由于還有外圍一個(gè)正方形邊框,我們可以用偽元素來繪制(當(dāng)然,這是在可以使用的情況下),好處是可以直接用border畫邊框,這比用漸變方便的多,然后加號(hào)就是兩段線性漸變,如下
image-20230629190245242用代碼實(shí)現(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;
}
調(diào)整一下間距,效果如下
image-20230629190549851現(xiàn)在都是加號(hào)(?),看不出哪些是展開的,所以還需要繪制減號(hào)(?),可以用[open]屬性來判斷,相較于加號(hào)(?)而言,只需要一個(gè)線性漸變就行了,實(shí)現(xiàn)如下
details[open]>summary::before{
????background:?linear-gradient(#999,?#999)?50%/10px?1px?no-repeat;
}
現(xiàn)在就可以區(qū)分哪些是展開,哪些是折疊的了
image-20230629191242729到了這一步,其實(shí)還有一個(gè)小問題,有些是不能繼續(xù)展開的,因?yàn)橐呀?jīng)到了最底層,沒有內(nèi)容了,所以希望在沒有展開內(nèi)容的時(shí)候不顯示加號(hào)(?)或者減號(hào)(?),這應(yīng)該如何判斷呢?
其實(shí)很簡(jiǎn)單,在沒有展開內(nèi)容的情況下,其實(shí)只有summary單個(gè)標(biāo)簽,就像這種結(jié)構(gòu)
<details>
??<summary>文件</summary>
??<!--沒有內(nèi)容了-->
</details>
提到單個(gè)標(biāo)簽,可以想到:only-child偽類,所以可以這樣重置一下
summary:only-child::before{
??display:?none
}
還有另外一種做法,那就是借助:not偽類,直接在前面的選擇器上加一層判斷
summary:not(:only-child)::before{
????/*排除單個(gè)summary的情況*/
}
這樣會(huì)更加優(yōu)雅~效果如下
image-20230629193251906這樣就能輕易的看出哪些是不能展開的了
三、繪制連接線
最后就是繪制連接線,也是 CSS 最靈活的、最有趣的一部分。
先從繪制實(shí)線開始,這樣比較容易。
直接繪制可能有些難度,我們可以分解開來,一部分是垂直的,指向樹的每個(gè)標(biāo)題部分,所以直接繪制在summary上,還有一部分是豎直的,并且豎直部分會(huì)包含整個(gè)展開部分,因此可以把線條繪制在details上,用代碼實(shí)現(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;
}
效果如下
image-20230629195318472看著好像有些凌亂?確實(shí)有很多線是多余的,比如樹的最后一個(gè)節(jié)點(diǎn),垂直線段不應(yīng)該繼續(xù)向下延伸了,最左側(cè)的線也是多余的,下面是示意圖,我們其實(shí)想要右邊那樣的效果
image-20230629195757152首先是最左側(cè)的線段,其實(shí)就是最外層,也就是第一層,要去除很簡(jiǎn)單,直接選中第一層的details以及下面的summary就行了,這里可以用子選擇器>來實(shí)現(xiàn)
.tree>details,
.tree>details>summary{
??/*去除最外層的連接線*/
??background:?none
}
效果如下
image-20230629200154002然后就是每層的最后一個(gè)子節(jié)點(diǎn),如何將垂直線段去除呢?其實(shí)可以從HTML結(jié)構(gòu)上入手,最后一層,其實(shí)就是最后一個(gè)details,所以將最后一個(gè)的背景尺寸改為剛好和垂直線段吻合
details:last-child{
??background-size:?1px?23px;
}
為了區(qū)分,下面將這一部分用藍(lán)色表示
image-20230629200946007還有一個(gè)小優(yōu)化,現(xiàn)在最左側(cè)第一層都是分開的,看著有些零散,這是因?yàn)榍懊孢@一步將所有最后一層的垂直線段都去掉了,所以需要還原這種情況,可以用子選擇器>選到,如下
.tree>details:not(:last-child)>details:last-child{
???background-size:?1px?100%;
}
為了區(qū)分,下面將這一部分用紫色表示
image-20230629201352522實(shí)線畫出來了,虛線還遠(yuǎn)嗎?
同樣也可以用漸變實(shí)現(xiàn),只不過需要用repeating-linear-gradient,因?yàn)樘摼€其實(shí)是不斷重復(fù)的從實(shí)色到透明的漸變,示意如下
image-20230630111217400用代碼實(shí)現(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;
}
這樣就實(shí)現(xiàn)了文章開頭效果了
Kapture 2023-06-28 at 19.47.32下面是完整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;
}
你也可以查看以下任意鏈接:
- CSS tree with line (juejin.cn) [4]
- CSS tree with line (codepen.io) [5]
- CSS tree with line (runjs.work) [6]
四、總結(jié)一下
以上就是本文的全部內(nèi)容了,可以看到全部由 CSS 繪制而成,沒有用到任何圖片,是不是很簡(jiǎn)單呢?下面總結(jié)一下實(shí)現(xiàn)要點(diǎn)
-
details和summary原生支持展開收起 -
details和summary支持多層嵌套,這樣就得到了簡(jiǎn)易的樹狀結(jié)構(gòu) -
逐層縮進(jìn)可以通過給
details添加內(nèi)邊距實(shí)現(xiàn) -
summary的黑色三角形是通過list-style生成的,可以更改display屬性去除 -
利用偽元素可以輕易實(shí)現(xiàn)
border邊框,這比用漸變方便的多 - 加號(hào)其實(shí)是兩段線性漸變疊加而成,減號(hào)一段漸變就夠了
-
連接線可以分成兩段,垂直線段繪制在 details 上,水平線段繪制在
summary上 -
多余的線段可以通過
:last-child和子選擇器>去除 -
虛線其實(shí)是不斷重復(fù)的從實(shí)色到透明的漸變,可以用
repeating-linear-gradient繪制
相比于現(xiàn)有的組件庫,原生實(shí)現(xiàn)最大的好處就是靈活性,合理運(yùn)用選擇器,各式各樣的設(shè)計(jì)都能輕易實(shí)現(xiàn),組件庫可兼顧不了這么多。另外,兼容性方面也非常不錯(cuò),主流瀏覽器均支持,IE 上雖然不支持 details 和 summary,但是通過 polyfill[7] 解決,總的來說非常實(shí)用的,大可以放心使用。最后,如果覺得還不錯(cuò),對(duì)你有幫助的話,歡迎點(diǎn)贊、收藏、轉(zhuǎn)發(fā)???
參考資料
[1]CSS 實(shí)現(xiàn)樹狀結(jié)構(gòu)目錄: https://juejin.cn/post/7095580369537204238
[2]details: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/details
[3]summary: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/summary
[4]CSS tree with line (juejin.cn): https://code.juejin.cn/pen/7250313681941037093
[5]CSS tree with line (codepen.io): https://codepen.io/xboxyan/pen/poQeNPN
[6]CSS tree with line (runjs.work): https://runjs.work/projects/86f2cac77080419a
[7]polyfill: https://github.com/javan/details-element-polyfill
