{T}

过渡与动画

D3 的过渡系统(Transition)基于 d3-transition 模块,通过插值器在两个状态之间平滑动画,是数据可视化中表达"变化"的核心手段。

1. 过渡基础

1.1 基本语法

javascript
import { select, transition } from 'd3'

// 在元素上启动过渡
select('circle')
  .transition()
  .duration(750)       // 持续时间 750ms
  .delay(0)            // 延迟 0ms
  .ease(d3.easeCubic)  // 缓动函数
  .attr('r', 20)       // 目标属性
  .style('fill', 'red') // 目标样式

1.2 过渡 vs 直接设置

javascript
// ❌ 直接设置(瞬间跳变)
selection.attr('cx', 300)

// ✅ 过渡(平滑动画)
selection.transition().duration(500).attr('cx', 300)

1.3 过渡生命周期

图表渲染中…

2. 缓动函数

2.1 内置缓动

javascript
import { easeLinear, easeQuad, easeCubic, easeElastic, easeBounce, easeBack } from 'd3'

selection.transition()
  .duration(1000)
  .ease(easeElastic.amplitude(1).period(0.4))  // 弹性效果
  .attr('cy', 300)
缓动函数效果适用场景
easeLinear匀速进度条
easeQuad二次加减速通用
easeCubic三次加减速(默认)通用(推荐)
easeBack轻微过冲弹出效果
easeElastic弹性振荡趣味交互
easeBounce弹跳落地下落动画

2.2 自定义缓动

javascript
// 组合缓动
const customEase = t => t * t * (3 - 2 * t)  // smoothstep

selection.transition()
  .ease(customEase)
  .attr('x', 500)

3. 过渡控制

3.1 命名过渡(并行)

javascript
// 同一元素上并行执行多个过渡(需不同名称)
select('circle')
  .transition('position')
  .duration(1000)
  .attr('cx', 300)

select('circle')
  .transition('color')
  .duration(500)
  .style('fill', 'red')

3.2 链式过渡(串行)

javascript
select('circle')
  .transition()
  .duration(500)
  .attr('cx', 300)
  .transition()  // 上一个完成后开始
  .duration(500)
  .attr('cy', 200)
  .transition()
  .duration(300)
  .attr('r', 5)

3.3 过渡事件

javascript
select('circle')
  .transition()
  .duration(1000)
  .on('start', function() {
    d3.select(this).attr('stroke', 'orange')
  })
  .on('interrupt', function() {
    console.log('过渡被中断')
  })
  .on('end', function() {
    d3.select(this).attr('stroke', 'none')
    console.log('过渡完成')
  })
  .attr('cx', 500)

3.4 中断过渡

javascript
// 中断所有过渡
select('circle').interrupt()

// 中断指定名称的过渡
select('circle').interrupt('position')

4. 数据更新过渡

4.1 柱状图更新动画

javascript
function updateBars(data) {
  const t = transition().duration(750)

  svg.selectAll('.bar')
    .data(data, d => d.name)
    .join(
      enter => enter.append('rect')
        .attr('class', 'bar')
        .attr('x', d => x(d.name))
        .attr('width', x.bandwidth())
        .attr('y', height)      // 从底部开始
        .attr('height', 0)
        .attr('fill', 'steelblue')
        .call(enter => enter.transition(t)
          .attr('y', d => y(d.value))
          .attr('height', d => height - y(d.value))
        ),
      update => update
        .call(update => update.transition(t)
          .attr('y', d => y(d.value))
          .attr('height', d => height - y(d.value))
        ),
      exit => exit
        .call(exit => exit.transition(t)
          .attr('y', height)
          .attr('height', 0)
          .remove()
        )
    )

  // 坐标轴也过渡
  svg.select('.y-axis')
    .transition(t)
    .call(axisLeft(y))
}

4.2 折线图路径过渡

javascript
function updateLine(newData) {
  const path = svg.select('.line-path')

  path.transition()
    .duration(750)
    .attrTween('d', function() {
      const previous = d3.select(this).attr('d')
      const current = lineGenerator(newData)
      return d3.interpolatePath(previous, current)
    })
}
路径过渡需要 d3-interpolate-path

D3 内置的字符串插值对路径效果不佳。使用 d3-interpolate-path 库可获得平滑的路径变形动画。

5. 交错动画(Stagger)

javascript
// 柱子依次升起
svg.selectAll('.bar')
  .data(data)
  .join('rect')
  .attr('y', height)
  .attr('height', 0)
  .transition()
  .duration(600)
  .delay((d, i) => i * 80)  // 每个柱子延迟 80ms
  .attr('y', d => y(d.value))
  .attr('height', d => height - y(d.value))
javascript
// 从中心向外扩散
.transition()
.delay((d, i, nodes) => {
  const [cx, cy] = [width / 2, height / 2]
  const dx = x(d.x) - cx
  const dy = y(d.y) - cy
  return Math.sqrt(dx * dx + dy * dy) * 2  // 距离越远延迟越大
})

6. 性能优化

策略说明
限制同时过渡数量超过 500 个元素同时过渡会卡顿
使用 attr 而非 styleSVG 属性过渡比 CSS 样式过渡性能更好
避免过渡 d 属性路径插值计算量大,优先过渡 transform
使用 CSS transition 替代简单属性变化用 CSS 更高效
及时 interrupt新数据到来时中断旧过渡,避免队列堆积
过渡不是万能的

对于高频更新(如实时数据流 > 10fps),不要使用 D3 transition。直接设置属性或使用 requestAnimationFrame 手动控制。

7. 与 CSS 动画的对比

维度D3 TransitionCSS Transition/Animation
控制粒度逐元素、逐属性类级别
数据驱动天然支持需手动映射
中断/链式完整 API有限
性能JS 驱动,较慢GPU 加速,更快
适用场景数据更新动画交互状态动画(hover)

推荐策略:交互反馈(hover/active)用 CSS,数据变化动画用 D3 transition。