HTML / 元件 / tree viewer
===
###### tags: `frontend / HTML`
###### tags: `frontend`, `HTML`, `HTML5`, `tree`, `viewer`, `details`, `summary`, `folder`, `file`, `icon`, `free`
<br>
[TOC]
<br>
## 專業級 TreeViewr
- https://github.com/SlinkyProject/slurm-operator/commit/431d7de00cf07bd3259e43f72b9a984f016bfd12

<br>
## 箭頭範例 - 文字樣式

- ### code
```html=
<html>
<style>
details {
margin-left: 1em; /* 基本縮排 */
}
details[open] > summary::before {
content: "▼ "; /* 展開狀態的 icon */
font-size: 0.8em;
}
details > summary::before {
content: "▶ "; /* 收合狀態的 icon */
font-size: 0.8em;
}
summary {
cursor: pointer;
list-style: none; /* 移除預設符號 */
}
summary::-webkit-details-marker {
display: none; /* 移除 Chrome 預設箭頭 */
}
/* 針對不同層級加大縮排 */
details details {
margin-left: 1.5em;
}
</style>
<body>
<details open>
<summary>旅遊</summary>
<details close>
<summary>機場用語</summary>
</details>
<details close>
<summary>飯店用語</summary>
<details open>
<summary>櫃台用語</summary>
</details>
<details open>
<summary>餐廳用語</summary>
</details>
</details>
</details>
</body>
</html>
```
- ### 重點說明
1. **縮排**:用 `margin-left` 依層級加大,`details details` 可以選到第二層再加縮排。
2. **icon**:用 `summary::before` 加文字或 SVG,並用 `details[open]` 判斷展開狀態切換。
3. **移除預設箭頭**:
* `summary::-webkit-details-marker { display: none; }` 可隱藏 Chrome 預設箭頭。
4. **可改成 SVG 圖示**:如果不想用 "▶ / ▼",可以用背景圖片或內嵌 `<svg>`。
<br>
---
<br>
## 箭頭範例 - svg 圖片樣式

- ### code
```html=
<html>
<style>
details {
margin-left: 1em; /* 第一層縮排 */
}
summary {
cursor: pointer;
list-style: none; /* 移除預設符號 */
padding-left: 1.2em; /* 預留背景圖位置 */
background-repeat: no-repeat;
background-position: 0 50%; /**/
background-size: 1em 1em;
}
/* 收合狀態的箭頭 (右向) */
summary {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><polygon points='2,1 8,5 2,9' fill='%23333'/></svg>");
}
/* 展開狀態的箭頭 (下向) */
details[open] > summary {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><polygon points='1,2 9,2 5,8' fill='%23333'/></svg>");
}
</style>
<body>
<details open>
<summary>旅遊</summary>
<details close>
<summary>機場用語</summary>
</details>
<details close>
<summary>飯店用語</summary>
<details open>
<summary>櫃台用語</summary>
</details>
<details open>
<summary>餐廳用語</summary>
</details>
</details>
</details>
</body>
</html>
```
- `background-position: 0 50%;`
這是設定**背景圖片的位置**,它有兩個值:
1. **`0`** → **水平位置**
* 表示背景圖片的左邊對齊元素的最左邊(`0`% = 左邊界),也可以寫 `left`,效果一樣。
* `100`% = 右邊界),也可以寫 `right`,效果一樣。
2. **`50%`** → **垂直位置**
* 表示背景圖片的中間點對齊元素的垂直中間(`50%` = 垂直置中)。
* 也可以寫 `center`,效果一樣。
🔍 **簡單理解**:
- 把背景圖片放在元素**最左邊**,並且**垂直置中**,所以不管文字多高,icon 都會在中間。
- 如果改成 `background-position: 5px center;`,它就會往右移 5px 再垂直置中。
- ### 重點特色
1. **背景圖是 SVG Data URI**
* 好處:不用額外檔案,顏色(`fill='%23333'`)可以直接改成自己要的十六進位色碼。
* SVG 右箭頭與下箭頭是兩個不同的 Data URI。
2. **移除預設箭頭**
* 不用 `summary::-webkit-details-marker`,因為背景圖本身就蓋掉預設箭頭了。
---
<br>
## 箭頭範例 - 使用 border 樣式

- ### code
```html=
<html>
<style>
/* 樹狀結構容器 */
.tree details {
padding-left: 1.2em; /* 縮排空間 */
position: relative;
}
/* 標題行樣式 */
.tree summary {
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
font-family: Arial, sans-serif;
}
/* 移除預設箭頭 */
.tree summary::-webkit-details-marker {
display: none;
}
/* 自訂箭頭圖示(初始為收合狀態) */
.tree summary::before {
content: "";
display: inline-block;
width: 0.5em;
height: 0.5em;
border-right: 2px solid #555;
border-bottom: 2px solid #555;
transform: rotate(-45deg); /* ▶ 方向 */
margin-left: 0.5em;
margin-right: 0.5em;
transition: transform 0.2s ease;
}
/* 展開狀態旋轉箭頭 */
.tree details[open] > summary::before {
transform: rotate(45deg); /* ▼ 方向 */
}
/* 第二層開始額外縮排 */
.tree details details {
padding-left: 1.5em;
}
/* 顏色與 hover 效果(可改) */
.tree summary:hover {
background-color: #f0f0f0;
}
</style>
<body>
<div class="tree">
<details open>
<summary>旅遊</summary>
<details close>
<summary>機場用語</summary>
</details>
<details close>
<summary>飯店用語</summary>
<details open>
<summary>櫃台用語</summary>
</details>
<details open>
<summary>餐廳用語</summary>
</details>
</details>
</details>
</div>
</body>
</html>
```
- `display: flex; align-items: center;`
1. **水平排列**:箭頭(`::before`)和文字會在同一行,不會因為換行或 baseline 不齊而跳位。
2. **垂直置中**(`align-items: center;`):確保箭頭與文字的中線對齊,看起來比較整齊。
- ### 重點特色
1. **多層縮排**:每一層 `<details>` 都會自動加左邊縮排。
2. **旋轉箭頭**:用 `::before` 畫一個小箭頭,收合時指向右邊,展開時旋轉指向下方。
3. **可無限層級**:無論幾層 `<details>` 都會自動套用縮排與箭頭效果。
4. **hover 高亮**:滑鼠移到項目上會加背景色(可自行調整)。
---
<br>
## icon 範例 - 樹狀檢視器(Tree Viewer)

- ### code
```html=
<html>
<style>
.tree {
font-family: Arial, system-ui, -apple-system, "Noto Sans TC", sans-serif;
line-height: 1.6;
}
/* 每層資料夾的縮排 */
.tree details {
padding-left: 1.2em;
}
.tree details details {
padding-left: 1.5em;
}
/* summary 樣式 */
.tree summary {
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
gap: 6px;
border-radius: 6px;
padding: 2px 4px;
}
.tree summary:hover { background: #f3f4f6; }
/* 移除預設箭頭 */
.tree summary::-webkit-details-marker { display: none; }
/* 資料夾 icon */
.tree details > summary .icon::after { content: "📁"; }
.tree details[open] > summary .icon::after { content: "📂"; }
/* 檔案 icon */
.tree .file::before { content: "📄"; margin-right: 6px; }
/* 檔案列縮排與 hover 效果 */
.tree .file {
display: flex;
align-items: center;
padding: 2px 4px 2px calc(1.2em + 2px); /* 與資料夾 icon 對齊 */
border-radius: 6px;
}
.tree .file:hover { background: #f9fafb; }
</style>
<div class="tree">
<details open>
<summary><span class="icon"></span>旅遊</summary>
<details open>
<summary><span class="icon"></span>機場用語</summary>
<div class="file">值機常用句.pdf</div>
<div class="file">安檢問答.md</div>
</details>
<details>
<summary><span class="icon"></span>飯店用語</summary>
<details open>
<summary><span class="icon"></span>櫃台用語</summary>
<div class="file">入住/退房句型.docx</div>
</details>
<details open>
<summary><span class="icon"></span>餐廳用語</summary>
<div class="file">點餐流程.txt</div>
<div class="file">過敏原詢問.md</div>
</details>
</details>
</details>
</div>
</body>
</html>
```
- ### 重點
* **沒有箭頭**:完全移除箭頭相關 CSS,只用資料夾圖示來提示展開 / 收起狀態。
* **資料夾圖示會變化**:
* 收起時 📁
* 展開時 📂
* **檔案圖示**:使用 📄 並自動縮排到與資料夾文字對齊。
* **支援多層巢狀**:每多一層 `<details>` 就會自動縮排。
<br>
## 討論
### `details[]` vs `details`
- ### Question
```
.tree details[] > summary .icon::before {
content: "📁"
}
.tree details > summary .icon::before {
content: "📁"
}
```
前後差在哪裡?
- ### gpt-5 (2025/08/13)
你寫的兩個選擇器
```css
.tree details[] > summary .icon::before { ... }
```
和
```css
.tree details > summary .icon::before { ... }
```
最大的差別在 **`details[]`** 這個部分。
---
### 1. `.tree details > summary .icon::before`
這是**正常有效**的選擇器,意思是:
> 在 `.tree` 元素裡,選擇 **任何 `<details>`**,其**直接子元素 `<summary>`** 裡的 `.icon` 元素,然後套用 `::before` 內容。
範例結構匹配:
```html
<div class="tree">
<details>
<summary>
<span class="icon"></span>
</summary>
</details>
</div>
```
這裡 `.icon::before` 就會被套用。
---
### 2. `.tree details[] > summary .icon::before`
`details[]` 在 CSS 選擇器語法中,**`[]` 是屬性選擇器**的語法,**裡面必須要有屬性條件**,像:
* `details[open]` → 選展開的 `<details>`
* `details[title]` → 選有 `title` 屬性的 `<details>`
* `details[data-id="123"]` → 選有特定屬性值的 `<details>`
如果你什麼都不寫成 `details[]`,這在 CSS 裡是**不合法的選擇器**,瀏覽器會忽略這條規則,等於沒作用。
---
### 3. 正確的展開 / 收起資料夾寫法
如果你要針對展開狀態換 icon,就應該寫成:
```css
/* 收起資料夾 */
.tree details > summary .icon::before {
content: "📁";
}
/* 展開資料夾(有 open 屬性) */
.tree details[open] > summary .icon::before {
content: "📂";
}
```
這樣在 `<details>` 展開時(`open` 屬性存在)就會自動切換。
<br>
{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}