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

D3.js实现排序算法动画触发浏览器崩溃问题求助

D3.js冒泡排序动画浏览器崩溃问题修复

问题描述

尝试用D3.js实现冒泡排序动画,点击开始按钮后浏览器直接崩溃,修改.data(data)为.data([data[j], data[j+1]])后仍存在性能问题。

问题根源

同步嵌套for循环会瞬间触发大量D3过渡动画,JS单线程特性导致所有过渡任务被一次性推入事件队列,浏览器资源被耗尽最终崩溃。循环不会等待上一次动画完成就执行下一次交换,导致过渡任务堆积。

修复方案

改用异步流程控制,让每一步排序交换完成后再执行下一次动画,避免任务堆积。以下是修正后的完整代码:

import * as d3 from "d3";

const drawVisualization = (data: number[]) => {
  const margin = { top: 10, right: 30, bottom: 30, left: 40 };
  const width = 800 - margin.left - margin.right;
  const height = 400 - margin.top - margin.bottom;

  // 清除旧的可视化内容
  d3.select(".visualization").selectAll("*").remove();

  const svg = d3
    .select(".visualization")
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", `translate(${margin.left}, ${margin.top})`);

  // 为每个数据绑定唯一标识,避免D3数据匹配错误
  const rects = svg.selectAll("rect")
    .data(data, (d, i) => i) // 用索引作为key,确保数据和元素正确对应
    .enter()
    .append("rect")
    .attr("x", (_d, i) => i * (width / data.length))
    .attr("y", (d) => height - d)
    .attr("width", width / data.length - 1)
    .attr("height", (d) => d)
    .on("mouseover", function (_d, i) {
      svg.append("text")
        .attr("class", `label-${i}`)
        .text(data[i])
        .attr("x", i * (width / data.length) + (width / data.length) / 2)
        .attr("y", height - data[i] - 20)
        .attr("font-size", "14px")
        .attr("fill", "blue")
        .attr("text-anchor", "middle");

      d3.select(this).style("opacity", 0.85);
    })
    .on("mouseleave", function (_d, i) {
      svg.select(`.label-${i}`).remove();
      d3.select(this).style("opacity", 1);
    });

  const startButton = document.querySelector(".run-sorting-btn") as HTMLButtonElement;

  // 异步冒泡排序函数
  const bubbleSort = async () => {
    const arr = [...data]; // 复制原数组,避免修改原始数据
    const rectElements = svg.selectAll("rect");

    for (let i = 0; i < arr.length - 1; i++) {
      for (let j = 0; j < arr.length - i - 1; j++) {
        if (arr[j] > arr[j + 1]) {
          // 交换数组元素
          [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];

          // 等待动画完成
          await new Promise<void>((resolve) => {
            // 只更新被交换的两个矩形的位置
            rectElements.filter((_d, idx) => idx === j || idx === j + 1)
              .data([arr[j], arr[j + 1]], (d, idx) => idx)
              .transition()
              .duration(500)
              .attr("x", (_d, idx) => (j + idx % 2) * (width / arr.length))
              .on("end", resolve);
          });
        }
      }
    }
    // 排序完成后更新所有矩形的最终位置(可选,确保对齐)
    rectElements.data(arr, (d, i) => i)
      .transition()
      .duration(200)
      .attr("x", (_d, i) => i * (width / arr.length));
  };

  startButton.addEventListener("click", () => {
    bubbleSort();
  });
};

export default drawVisualization;

关键修改点

  • 异步流程控制:用async/await配合Promise,确保每一步动画完成后再执行下一次排序交换,避免任务堆积。
  • 数据绑定key:绑定数据时使用索引作为唯一标识(.data(data, (d,i)=>i)),避免D3在数据交换后匹配错误元素。
  • 精准更新元素:只对交换的两个矩形执行过渡动画,而不是所有矩形,减少性能开销。
  • 复制原数组:排序时操作数组副本,避免修改原始输入数据,保证可视化的可重复性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 07:05:13