用原生js通过data-toggle绑定点击事件,用nextelementsibling查找相邻submenu并切换display或collapsed类实现折叠展开,支持图标替换、css过渡动画及localstorage记忆状态。

用 DOM 操作实现树形菜单的折叠展开,核心是控制子菜单(<ul></ul> 或 <div>)的显示与隐藏,并切换父级箭头或加减号图标。不需要框架,纯原生 JS 就能完成,关键是正确绑定事件、精准查找兄弟/子元素、维护状态。
<h3>结构准备:语义化嵌套 HTML</h3>
<p>树形菜单本质是嵌套的列表,每一级用 <code><ul></ul> 包裹子项,父项用 <li>,并为可折叠项添加标识(如 data-toggle="tree"):
注意:.submenu 默认隐藏(CSS 中设 display: none),.toggle 是点击目标。
JS 控制:绑定点击 + 切换显隐
获取所有可折叠项,遍历绑定点击事件。点击时找到紧邻的下一个 <ul class="submenu"></ul> 并切换其 display 状态:
document.querySelectorAll('[data-toggle="tree"]').forEach(toggle => {
toggle.addEventListener('click', function() {
const submenu = this.nextElementSibling;
if (!submenu || !submenu.classList.contains('submenu')) return;
if (submenu.style.display === 'none' || submenu.style.display === '') {
submenu.style.display = 'block';
this.textContent = this.textContent.replace('?', '?').replace('⚙️', '?');
} else {
submenu.style.display = 'none';
this.textContent = this.textContent.replace('?', '?').replace('?', '⚙️');
}
});
});
- 用
nextElementSibling精准找兄弟<ul></ul>,避免误操作其他元素 - 检查
submenu存在且含submenu类,增强健壮性 - 图标替换用字符串替换,轻量直观;也可改用 CSS class 控制图标(推荐)
进阶建议:用 class 替代 style 控制显隐
直接操作 style.display 不利于样式复用和过渡动画。更推荐通过增删 class 来控制:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
.submenu { transition: max-height 0.2s ease-out; overflow: hidden; }
.submenu.collapsed { max-height: 0; opacity: 0; }
.submenu:not(.collapsed) { max-height: 500px; opacity: 1; }
JS 改为:
submenu.classList.toggle('collapsed');
- 支持 CSS 过渡动画(如高度渐变、淡入淡出)
- 不干扰内联样式,便于全局维护
- 可配合
max-height实现平滑收起(需预估最大高度)
可选增强:记忆展开状态 + 递归初始化
若希望刷新后保持上次展开状态,可用 localStorage 记录路径:
// 初始化时读取
const expandedKeys = JSON.parse(localStorage.getItem('treeExpanded') || '[]');
document.querySelectorAll('.submenu').forEach((submenu, i) => {
const isOpen = expandedKeys.includes(i);
submenu.classList.toggle('collapsed', !isOpen);
});
// 点击后保存
toggle.addEventListener('click', function() {
const submenu = this.nextElementSibling;
const index = Array.from(document.querySelectorAll('.submenu')).indexOf(submenu);
const expanded = JSON.parse(localStorage.getItem('treeExpanded') || '[]');
if (submenu.classList.contains('collapsed')) {
expanded.push(index);
} else {
const idx = expanded.indexOf(index);
if (idx > -1) expanded.splice(idx, 1);
}
localStorage.setItem('treeExpanded', JSON.stringify(expanded));
});
这样用户关闭再打开页面,菜单仍保持原样。










