D3.js可排序表格:如何让表头下拉框不触发排序?
问题与解决方案
需求
使用Flexbox+D3.js实现的表格,表头点击可触发对应列排序,但表头内的<select>下拉框点击时需排除排序触发,仅表头其他区域可正常触发排序。
实现方案
核心是阻止下拉框的点击事件冒泡到父级表头元素,同时优化事件绑定逻辑避免重复触发:
- 给下拉框绑定点击事件,调用
event.stopPropagation()阻止事件向上传递; - 绑定排序事件前先解绑旧事件,避免表格重绘后重复绑定导致多次触发;
- 优化排序状态切换逻辑,维护
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
相关产品推荐
相关产品推荐

