You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

D3.js可排序表格:如何让表头下拉框不触发排序?

问题与解决方案

需求

使用Flexbox+D3.js实现的表格,表头点击可触发对应列排序,但表头内的<select>下拉框点击时需排除排序触发,仅表头其他区域可正常触发排序。

实现方案

核心是阻止下拉框的点击事件冒泡到父级表头元素,同时优化事件绑定逻辑避免重复触发:

  1. 给下拉框绑定点击事件,调用event.stopPropagation()阻止事件向上传递;
  2. 绑定排序事件前先解绑旧事件,避免表格重绘后重复绑定导致多次触发;
  3. 优化排序状态切换逻辑,维护sortAsc/sortDesc类的正确切换。

修改后的完整代码

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<div id="table">
    <div class="grid">
      <div class="box hrbox name">
        <div style="display: flex;">
          <div>name</div>
          <select id="attr"></select>
        </div>
      </div>
      <div class="box hrbox dept">
        <div>dept</div>
      </div>
      <div class="box hrbox region">
        <div>region</div>
      </div>
      <div class="box hrbox items">
        <div>items</div>
      </div>
    </div>
    <div id="data-table"></div>
  </div>

CSS

.grid {
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
}

.grid>div {
  display: flex;
  flex-basis: calc(100%/4 - 28px);
  justify-content: center;
  flex-direction: column;
  padding: 10px;
}

.box {
  margin: 0;
}

.box {
  color: #000;
  border: .5px solid #ccc;
}

.hrbox {
  background-color: #ddd;
  color: #111;
  font-weight: bold;
  background-size: 1.5rem;
  background-repeat: no-repeat;
  background-position: center right;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%234f4f4f' d='M16.29 14.29L12 18.59l-4.29-4.3a1 1 0 00-1.42 1.42l5 5a1 1 0 001.42 0l5-5a1 1 0 00-1.42-1.42zM7.71 9.71L12 5.41l4.29 4.3a1 1 0 001.42 0 1 1 0 000-1.42l-5-5a1 1 0 00-1.42 0l-5 5a1 1 0 001.42 1.42z'/%3E%3C/svg%3E");
  text-align: left !important;
}

.hrbox.sortAsc {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%234f4f4f' d='M11.29 11.46a1 1 0 001.42 0l3-3A1 1 0 1014.29 7L12 9.34 9.71 7a1 1 0 10-1.42 1.46zm3 1.08L12 14.84l-2.29-2.3A1 1 0 008.29 14l3 3a1 1 0 001.42 0l3-3a1 1 0 00-1.42-1.42z'/%3E%3C/svg%3E");
}

.hrbox.sortDesc {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%234f4f4f' d='M12.71 12.54a1 1 0 00-1.42 0l-3 3A1 1 0 009.71 17L12 14.66 14.29 17a1 1 0 001.42 0 1 1 0 000-1.42zm-3-1.08L12 9.16l2.29 2.3a1 1 0 001.42 0 1 1 0 000-1.42l-3-3a1 1 0 00-1.42 0l-3 3a1 1 0 001.42 1.42z'/%3E%3C/svg%3E");
}

#attr {
  width: 60px;
  margin-left: 20px;
}

JavaScript

const data = [
  {
    "name": "Dave",
    "dept": "Marketing",
    "region": "South",
    "items": 28
  },
  {
    "name": "Amy",
    "dept": "IT",
    "region": "West",
    "items": 46
  },
  {
    "name": "John",
    "dept": "Sales",
    "region": "North",
    "items": 35
  },
  {
    "name": "Sarah",
    "dept": "Communications",
    "region": "North",
    "items": 13
  }
];

// 保存原始数据顺序,用于重置排序
const originalData = [...data];

drawTable(data);
sortTable(data);
initSelectHandler();

function drawTable(data) {
  d3.select('#data-table').remove();

  let table = d3.select('#table').append('div').attr('id', 'data-table');

  let row = table.selectAll('.row')
    .data(data)
    .enter()
    .append('div')
    .attr('class', 'row');

  let grid = row.append('div').attr('class', 'grid');

  grid.append('div').attr('class', 'box').html(d => d.name);
  grid.append('div').attr('class', 'box').html(d => d.dept);
  grid.append('div').attr('class', 'box').html(d => d.region);
  grid.append('div').attr('class', 'box').html(d => d.items);

  sortTable(data);
}

function sortTable(data) {
  // 先解绑旧事件,避免重复绑定导致多次触发
  d3.selectAll(".name,.dept,.region,.items").on('click', null);

  d3.selectAll(".name,.dept,.region,.items").on('click', function () {
    const header = d3.select(this);
    const sortVar = this.classList[2];
    const isAsc = header.classed("sortAsc");
    const isDesc = header.classed("sortDesc");
    
    // 重置所有表头的排序状态
    d3.selectAll(".hrbox").classed("sortAsc", false).classed("sortDesc", false);
    
    if (!isAsc && !isDesc) {
      // 切换为升序
      header.classed("sortAsc", true);
      data.sort((a, b) => d3.ascending(a[sortVar], b[sortVar]));
    } else if (isAsc) {
      // 切换为降序
      header.classed("sortAsc", false).classed("sortDesc", true);
      data.sort((a, b) => d3.descending(a[sortVar], b[sortVar]));
    } else {
      // 重置为原始顺序
      header.classed("sortDesc", false);
      data.length = 0;
      data.push(...originalData);
    }

    drawTable(data);
    initSelectHandler();
  });
}

function initSelectHandler() {
  // 阻止下拉框点击事件冒泡到表头
  d3.select('#attr').on('click', function(event) {
    event.stopPropagation();
  });
}

关键说明

  • event.stopPropagation():拦截下拉框的点击事件,避免触发父级表头的排序逻辑;
  • 事件解绑:每次绑定新排序事件前清空旧事件,防止表格重绘后同一元素绑定多个事件;
  • 状态管理:支持无排序、升序、降序的循环切换,同时重置其他表头状态,保证视觉一致性。

内容的提问来源于stack exchange,提问作者sprucegoose

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 12:00:55