
本文详解如何使用 d3.js 根据层级化 json 数据动态生成符合语义结构的响应式侧边导航栏,重点解决嵌套子菜单(如二级页面列表)的绑定与渲染问题,并提供可直接运行的完整示例代码。
本文详解如何使用 d3.js 根据层级化 json 数据动态生成符合语义结构的响应式侧边导航栏,重点解决嵌套子菜单(如二级页面列表)的绑定与渲染问题,并提供可直接运行的完整示例代码。
在现代前端开发中,将导航结构与数据解耦、实现声明式渲染是提升可维护性的关键。D3.js 不仅适用于可视化图表,其强大的数据绑定(data())与选择集(selection)机制,也使其成为构建动态 DOM 结构(如导航菜单)的理想工具。本文将手把手带你用 D3 构建一个支持标题分组(header)和带下拉页单(item + hasPages)的两级侧边导航栏。
核心思路:按类型分支处理,避免嵌套 data() 链式错误
原始代码报错 Uncaught TypeError: undefined is not iterable 的根本原因在于:
.data(d => d.hasItems) // ❌ 字段名错误(应为 hasPages),且未做空值防护
D3 的 .data() 方法要求返回值必须是可迭代对象(Array)或 null/undefined;若 d.hasPages 不存在或为 undefined,则传入 undefined 会导致后续 .enter().append() 报错。
正确做法是:在顶层绑定主数据后,对每个
完整实现代码(兼容 D3 v6/v7)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>D3 侧边导航栏教程</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.sidebar-nav { list-style: none; padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
.sidebar-header {
padding: 12px 20px;
font-size: 0.875rem;
font-weight: 600;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.5px;
background-color: #f8f9fa;
}
.sidebar-item > a {
display: block;
padding: 10px 20px;
color: #495057;
text-decoration: none;
transition: background-color 0.2s;
}
.sidebar-item > a:hover {
background-color: #e9ecef;
color: #343a40;
}
.sidebar-dropdown {
list-style: none;
padding-left: 0;
margin: 0;
}
.sidebar-dropdown .sidebar-item > a {
padding-left: 40px;
font-size: 0.875rem;
color: #6c757d;
}
</style>
</head>
<body>
<ul class="sidebar-nav"></ul>
<script>
const sidenavData = [
{ 'name': 'Header name 1', 'type': 'header' },
{
'name': 'Menu item 1',
'type': 'item',
'hasPages': [{ 'name': 'Page 1' }, { 'name': 'Page 2' }]
},
{
'name': 'Menu item 2',
'type': 'item',
'hasPages': [{ 'name': 'Report 1' }, { 'name': 'Report 2' }, { 'name': 'Report 3' }]
},
{ 'name': 'Header name 2', 'type': 'header' },
{ 'name': 'Notifications', 'type': 'item' },
{ 'name': 'Messages', 'type': 'item' }
];
const nav = d3.select('ul.sidebar-nav');
// 绑定主数据,创建顶层 <li>
nav.selectAll('li')
.data(sidenavData)
.enter()
.append('li')
.attr('class', d => d.type === 'header' ? 'sidebar-header' : 'sidebar-item')
.each(function(d) {
const li = d3.select(this);
if (d.type === 'header') {
// 标题:仅设置文本
li.text(d.name);
} else if (Array.isArray(d.hasPages) && d.hasPages.length > 0) {
// 带下拉菜单的项:先添加链接,再嵌套子列表
const link = li.append('a')
.attr('class', 'sidebar-link')
.text(d.name);
const dropdown = link.append('ul')
.attr('class', 'sidebar-dropdown');
dropdown.selectAll('li')
.data(d.hasPages)
.enter()
.append('li')
.attr('class', 'sidebar-item')
.append('a')
.attr('class', 'sidebar-link')
.text(page => page.name);
} else {
// 普通菜单项:仅添加链接
li.append('a')
.attr('class', 'sidebar-link')
.text(d.name);
}
});
</script>
</body>
</html>关键要点与最佳实践
- ✅ 字段健壮性检查:使用 Array.isArray(d.hasPages) && d.hasPages.length > 0 替代简单 d.hasPages 判断,防止 null 或 undefined 导致崩溃;
- ✅ 语义化结构优先:导航使用
- /
- 保证无障碍访问(a11y)与 SEO 友好,避免滥用 ;
- ✅ CSS 解耦设计:所有样式通过类名控制,便于主题切换与组件复用;
- ⚠️ 不推荐深层嵌套:本方案适用于两级(header → item → [pages])。若需三级及以上(如 pages 再含 subpages),建议改用递归组件(React/Vue)或 D3 的 nest() + 自定义递归渲染函数;
- ? 动态更新提示:如需后续更新数据(如权限变更后刷新菜单),可调用 nav.selectAll('li').data(newData).exit().remove() 并重新 .enter() 渲染,D3 会自动执行 enter/update/exit 三阶段更新。
通过以上实现,你已掌握用 D3 构建数据驱动导航的核心模式:以 each() 为逻辑分发器,按数据形态定制 DOM 结构,兼顾简洁性与健壮性。该模式可轻松扩展至面包屑、顶部导航、多级下拉等场景。
- 保证无障碍访问(a11y)与 SEO 友好,避免滥用











