# jsTree Theme 机制（本仓库版本）

本文整理 jsTree 在本仓库中的 theme 机制：工作原理、优缺点、可改进点，以及如何新增 theme（含“只在业务侧新增”与“纳入仓库构建”两种方式）。

> 说明：本仓库已移除“移动端额外主题开关”相关能力与样式，本文不包含那部分内容。

## 1. Theme 是什么

jsTree 的 theme 并不是“换一套 DOM”，而是 **一套稳定 DOM 骨架 + 一组约定的 CSS class + 主题样式文件**。

### 1.1 DOM 骨架（稳定协议）

jsTree 的节点结构会反复出现以下关键元素/类名（节选）：

- `.jstree`：实例容器
- `.jstree-container-ul`：根 UL
- `.jstree-node`：每个 LI 节点
- `.jstree-anchor`：节点可点击区域
- `.jstree-icon` / `.jstree-themeicon`：图标占位
- `.jstree-ocl`：展开/收起开关图标区域

骨架样式与一些插件的基础样式在 [base.less](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/base.less) 定义。

## 2. Theme 机制原理

### 2.1 配置入口：`core.themes`

theme 相关默认配置集中在 [jstree.js](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.js#L379-L424)：

- `name`：主题名（默认 `false`，初始化时会落到 `"default"`）
- `url`：主题 CSS 的加载方式（推荐手动引入；也支持自动注入 `<link>`）
- `dir`：当 `url === true` 时，用于拼接主题路径
- `dots/icons/ellipsis/stripes`：四个“展示开关”
- `variant`：主题变体名（例如 `small` / `large`）

初始化时会把这些设置同步到实例的 `_data.core.themes`，并调用 `set_theme()` 与 `set_theme_variant()`（见 [jstree.js](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.js#L898-L913)）。

### 2.2 主题切换：`set_theme(name, url)`

核心逻辑见 [set_theme](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.js#L4663-L4694)：

- 可选地加载 CSS
  - `theme_url === true`：会拼出 `dir + '/' + theme_name + '/style.css'`
  - `theme_url` 为字符串：直接注入 `<link rel="stylesheet" href="...">`
  - 注入的 URL 会记录在全局数组 `themes_loaded`，避免重复注入
- 切换容器 class
  - 移除旧主题 class：`jstree-<oldTheme>`
  - 添加新主题 class：`jstree-<theme>`
- 触发事件：`set_theme.jstree`

结论：**theme 的“切换”主要是 class 切换；CSS 是否加载取决于 `url` 参数。**

### 2.3 主题变体：`set_theme_variant(variant)`

逻辑见 [set_theme_variant](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.js#L4702-L4714)：

- 移除旧变体 class：`jstree-<theme>-<oldVariant>`
- 添加新变体 class：`jstree-<theme>-<variant>`

theme CSS 通常会提供：

- `.jstree-<theme>`（默认尺寸）
- `.jstree-<theme>-small`
- `.jstree-<theme>-large`

例如默认主题在 [main.less](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/main.less#L42-L57) 里通过 mixin 生成三套尺寸规则。

### 2.4 四个开关：dots / icons / stripes / ellipsis

这四个开关本质是 **给根 UL 加/去 class**，再由主题 CSS 响应这些 class。

对应 API 在 [jstree.js](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.js#L4721-L4852)：

- `show_dots()` / `hide_dots()`：切换 `jstree-no-dots`
- `show_icons()` / `hide_icons()`：切换 `jstree-no-icons`
- `show_stripes()` / `hide_stripes()`：切换 `jstree-striped`
- `show_ellipsis()` / `hide_ellipsis()`：切换 `jstree-ellipsis`

主题 CSS 会写类似选择器：

- `.jstree-<theme> > .jstree-no-dots ...`
- `.jstree-<theme> > .jstree-striped ...`

可在默认主题 CSS 中看到对应实现（例如 [default/style.css](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/default/style.css)）。

### 2.5 插件如何“挂钩”主题

插件普遍会把 `get_theme()` / `get_theme_variant()` 拼到 class 上，方便主题覆盖插件 UI。

典型例子：

- contextmenu：给菜单元素添加 `jstree-<theme>-contextmenu`（见 [jstree.contextmenu.js](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.contextmenu.js#L305-L313)）
- dnd：拖拽 helper/marker 带上 `jstree-<theme>` 与 `jstree-<theme>-<variant>`（见 [jstree.dnd.js](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.dnd.js#L120-L138)）

结论：新增 theme 时，如果你在意这些插件 UI 的一致性，就应该把这些 hook class 也覆盖掉（至少保证不破样式）。

## 3. 优点

- **解耦**：JS 只管行为与 class，CSS 负责视觉。
- **切换成本低**：切主题就是换一个 class；变体也是换一个 class。
- **可组合**：开关（dots/icons/stripes/ellipsis）与变体（small/large）可以自由组合。
- **复用与生成**：主题 LESS 用变量 + mixin 生成整套规则，减少手写重复（见 [mixins.less](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/mixins.less#L7-L104)）。

## 4. 缺点

- **自动加载 CSS 的方式偏脆**：依赖运行时注入 `<link>`，且没有 onload 保障；另外 `$.jstree.path` 的推断依赖 `script:last`（见 [jstree.js](file:///c:/Users/Administrator/Downloads/jstree-master/src/jstree.js#L44-L46)），在打包/异步加载场景容易不准。
- **CSS 不会卸载**：切主题只换 class，之前注入的 `<link>` 不会移除（`themes_loaded` 只是防重复注入）。
- **主题 CSS 体积偏大**：每个主题通常会包含一整套 base + 变体 + 插件相关样式；多主题同时加载时重复明显。
- **资源与坐标耦合**：默认主题通过 sprite + 坐标实现图标（见 [mixins.less](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/mixins.less#L17-L33)），替换资源需要维护坐标体系。

## 5. 改进空间（建议）

- **更稳的 CSS 加载策略**
  - 业务侧最佳实践：默认不要用自动注入 `<link>`，而是手动引入 CSS 并在构建系统里管理。
  - 如果确实需要自动加载：可考虑让 `set_theme()` 支持“加载完成后再切换 class / 触发事件”的模式（可用 Promise 或回调）。
- **减少多主题重复**
  - 把结构性 base 样式拆成单独文件只加载一次；主题文件仅包含“差异化部分”。
  - 逐步迁移到 CSS Variables：让颜色/间距通过变量切换，而不是加载多份主题 CSS。
- **图标体系现代化**
  - 以 SVG（或 CSS mask）替代 sprite，降低坐标维护成本并提升清晰度与可维护性。

## 6. 如何新增 Theme

### 6.1 方式 A：只在你的业务项目里新增（不改 jsTree 源码）

这是最推荐的方式：你只需要提供一份主题 CSS 并设置 `core.themes.name`。

1) 放置主题文件（约定目录结构）

- `/themes/<themeName>/style.css`
- 以及 CSS 引用到的图片资源（相对路径即可）

2) 写 CSS 时遵循约定

至少提供 `.jstree-<themeName>` 前缀的一套规则。最省事的做法是复制默认主题 CSS 并替换前缀。

3) 初始化使用该主题

```js
$('#tree').jstree({
  core: {
    themes: {
      name: 'mytheme',
      url: false,
      variant: 'small'
    }
  }
});
```

如果你选择让 jsTree 自动注入 CSS（不推荐，但可用）：

```js
$('#tree').jstree({
  core: {
    themes: {
      name: 'mytheme',
      url: true,
      dir: '/assets/jstree/themes'
    }
  }
});
```

### 6.2 方式 B：把新主题纳入本仓库构建产物（会影响 dist）

1) 新建主题目录与 LESS 入口

- `src/themes/<themeName>/style.less`

可参考现有主题：

- [default/style.less](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/default/style.less)
- [default-dark/style.less](file:///c:/Users/Administrator/Downloads/jstree-master/src/themes/default-dark/style.less)

2) 在 `style.less` 内设置变量并导入公共文件

你至少需要：

- `@theme-name: <themeName>;`
- `@image-path: "";`（如果资源就在当前目录）
- `@import "../mixins.less";`
- `@import "../base.less";`
- `@import "../main.less";`

3) 更新构建脚本

本仓库的构建（grunt）目前只编译两个主题，你需要把新主题加入 less 任务输出清单（见 [gruntfile.js](file:///c:/Users/Administrator/Downloads/jstree-master/gruntfile.js#L88-L110)）。

4) 生成物与发布清单

- dist 目录是生成物（见仓库 README 提示），纳入仓库后需要同步更新 `dist/themes/...`
- 如果你要让组件管理器收录新主题，还要更新类似 `component.json` / `composer.json` 的资源列表

## 7. 新主题的最小覆盖清单（建议）

如果你目标是“尽量少写 CSS”，优先保证这些层级不崩：

- `.jstree-<theme>`：容器背景、字体色、hover/selected 状态
- `.jstree-<theme> .jstree-ocl`：展开/收起的视觉（否则交互存在但看不见）
- `.jstree-<theme> .jstree-themeicon`：默认节点图标占位（可不做，但要考虑 `icons` 开关）
- `.jstree-<theme>-small / -large`：如果你要支持 `variant`

插件相关（按需）：

- `.jstree-<theme>-contextmenu`：右键菜单样式
- `#jstree-dnd.jstree-<theme>` / `#jstree-marker.jstree-<theme>`：拖拽 helper/marker

