构建基于 D3.js 的数据驱动侧边导航栏(支持多级嵌套)

2次阅读

构建基于 D3.js 的数据驱动侧边导航栏(支持多级嵌套)

本文详解如何使用 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() 报错。

正确做法是:在顶层绑定主数据后,对每个

  • 元素使用 .each() 进行类型判断与条件渲染
  • ——这既保持逻辑清晰,又规避了嵌套 selectAll().data() 的陷阱。

    完整实现代码(兼容 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 结构,兼顾简洁性与健壮性。该模式可轻松扩展至面包屑、顶部导航、多级下拉等场景。

    text=ZqhQzanResources