D3.js 过渡动画 #

过渡动画是 D3.js 的强大功能之一,它能够平滑地将元素从一个状态变换到另一个状态,使数据可视化更加生动和直观。

过渡基础 #

核心概念 #

text
┌─────────────────────────────────────────────────────────────┐
│                    过渡动画流程                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   初始状态 ──► 过渡 ──► 最终状态                             │
│                                                             │
│   attr('r', 5)  transition()  attr('r', 20)                │
│       │            │              │                         │
│       ▼            ▼              ▼                         │
│      r=5    ───►  动画中  ───►  r=20                        │
│                   插值计算                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

基本用法 #

javascript
d3.select('circle')
  .transition()
  .duration(1000)
  .attr('r', 20);

创建过渡 #

selection.transition() #

javascript
d3.selectAll('circle')
  .transition()
  .duration(1000)
  .attr('cx', 200)
  .attr('fill', 'red');

d3.transition() #

javascript
const t = d3.transition()
  .duration(1000)
  .ease(d3.easeLinear);

d3.selectAll('circle')
  .transition(t)
  .attr('r', 20);

命名过渡 #

javascript
const t = d3.transition('myTransition')
  .duration(1000);

d3.selectAll('circle')
  .transition(t)
  .attr('r', 20);

d3.selectAll('circle')
  .interrupt('myTransition');

过渡配置 #

duration - 持续时间 #

javascript
d3.select('circle')
  .transition()
  .duration(1000)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .duration(500)
  .attr('r', 20);

ease - 缓动函数 #

javascript
d3.select('circle')
  .transition()
  .ease(d3.easeLinear)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .ease(d3.easeBounce)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .ease(d3.easeElastic)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .ease(d3.easeCubicInOut)
  .attr('r', 20);

delay - 延迟 #

javascript
d3.selectAll('circle')
  .transition()
  .delay((d, i) => i * 100)
  .duration(500)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .delay(500)
  .attr('r', 20);

缓动函数 #

常用缓动函数 #

缓动函数 描述 效果
easeLinear 线性 匀速
easeQuad 二次方 慢-快-慢
easeCubic 三次方 更明显的慢-快-慢
easeSin 正弦 平滑
easeExp 指数 急加速
easeCircle 圆形 圆滑
easeBounce 弹跳 弹跳效果
easeElastic 弹性 弹簧效果
easeBack 回弹 超出后回弹

缓动变体 #

javascript
d3.easeQuadIn
d3.easeQuadOut
d3.easeQuadInOut

d3.easeCubicIn
d3.easeCubicOut
d3.easeCubicInOut

d3.easeElasticIn
d3.easeElasticOut
d3.easeElasticInOut

自定义缓动 #

javascript
d3.select('circle')
  .transition()
  .ease(t => t * t)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .ease(d3.easeElastic.amplitude(1).period(0.5))
  .attr('r', 20);

过渡属性 #

属性过渡 #

javascript
d3.select('circle')
  .transition()
  .attr('cx', 200)
  .attr('cy', 150)
  .attr('r', 30);

样式过渡 #

javascript
d3.select('circle')
  .transition()
  .style('fill', 'red')
  .style('opacity', 0.5);

文本过渡 #

javascript
d3.select('text')
  .transition()
  .duration(1000)
  .textTween(() => t => Math.round(t * 100) + '%');

过渡生命周期 #

事件监听 #

javascript
const transition = d3.select('circle')
  .transition()
  .duration(1000)
  .attr('r', 20);

transition.on('start', function() {
  console.log('Transition started');
  d3.select(this).attr('fill', 'orange');
});

transition.on('end', function() {
  console.log('Transition ended');
  d3.select(this).attr('fill', 'steelblue');
});

transition.on('interrupt', function() {
  console.log('Transition interrupted');
});

链式过渡 #

javascript
d3.select('circle')
  .transition()
  .duration(500)
  .attr('r', 30)
  .transition()
  .duration(500)
  .attr('fill', 'red')
  .transition()
  .duration(500)
  .attr('r', 10);

并行过渡 #

javascript
d3.select('circle')
  .transition()
  .duration(1000)
  .attr('r', 30);

d3.select('rect')
  .transition()
  .duration(1000)
  .attr('width', 200);

过渡控制 #

interrupt - 中断过渡 #

javascript
d3.selectAll('circle')
  .interrupt();

d3.selectAll('circle')
  .interrupt('myTransition');

selection.interrupt #

javascript
d3.select('circle')
  .interrupt()
  .transition()
  .attr('r', 10);

isActive - 检查过渡状态 #

javascript
const active = d3.active(circle.node());
if (active) {
  console.log('Transition is active');
}

过渡插值 #

默认插值 #

javascript
d3.select('circle')
  .transition()
  .attr('cx', 200);

d3.select('circle')
  .transition()
  .style('fill', 'red');

自定义插值器 #

javascript
d3.select('circle')
  .transition()
  .duration(1000)
  .attrTween('cx', function() {
    const interpolate = d3.interpolate(0, 200);
    return t => interpolate(t);
  });

d3.select('circle')
  .transition()
  .styleTween('fill', function() {
    return d3.interpolateRgb('blue', 'red');
  });

颜色插值 #

javascript
d3.interpolateRgb('blue', 'red');
d3.interpolateHsl('blue', 'red');
d3.interpolateHcl('blue', 'red');
d3.interpolateLab('blue', 'red');
d3.interpolateColor('blue', 'red');

数值插值 #

javascript
d3.interpolateNumber(0, 100);
d3.interpolateRound(0, 100);
d3.interpolateArray([0, 1], [10, 20]);
d3.interpolateObject({x: 0}, {x: 100});

过渡实战示例 #

柱状图动画 #

javascript
const data = [10, 20, 30, 40, 50];

const bars = svg.selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', (d, i) => i * 60)
  .attr('y', height)
  .attr('width', 50)
  .attr('height', 0);

bars.transition()
  .duration(1000)
  .delay((d, i) => i * 100)
  .ease(d3.easeCubicOut)
  .attr('y', d => height - d * 5)
  .attr('height', d => d * 5);

数据更新动画 #

javascript
function update(data) {
  const bars = svg.selectAll('rect')
    .data(data, (d, i) => i);
  
  bars.enter()
    .append('rect')
    .attr('x', (d, i) => i * 60)
    .attr('y', height)
    .attr('width', 50)
    .attr('height', 0)
    .merge(bars)
    .transition()
    .duration(500)
    .attr('y', d => height - d * 5)
    .attr('height', d => d * 5);
  
  bars.exit()
    .transition()
    .duration(500)
    .attr('y', height)
    .attr('height', 0)
    .remove();
}

路径动画 #

javascript
const line = d3.line()
  .x(d => d.x)
  .y(d => d.y);

const path = svg.append('path')
  .datum(data)
  .attr('d', line)
  .attr('fill', 'none')
  .attr('stroke', 'steelblue');

const totalLength = path.node().getTotalLength();

path
  .attr('stroke-dasharray', totalLength)
  .attr('stroke-dashoffset', totalLength)
  .transition()
  .duration(2000)
  .ease(d3.easeLinear)
  .attr('stroke-dashoffset', 0);

饼图动画 #

javascript
const pie = d3.pie()
  .value(d => d.value);

const arc = d3.arc()
  .innerRadius(0)
  .outerRadius(100);

const arcs = svg.selectAll('path')
  .data(pie(data))
  .enter()
  .append('path')
  .attr('fill', (d, i) => d3.schemeCategory10[i]);

arcs.transition()
  .duration(1000)
  .attrTween('d', function(d) {
    const interpolate = d3.interpolate(
      { startAngle: 0, endAngle: 0 },
      d
    );
    return t => arc(interpolate(t));
  });

散点图动画 #

javascript
const circles = svg.selectAll('circle')
  .data(data)
  .enter()
  .append('circle')
  .attr('cx', d => d.x)
  .attr('cy', height)
  .attr('r', 0);

circles.transition()
  .duration(1000)
  .delay((d, i) => i * 50)
  .ease(d3.easeBounce)
  .attr('cy', d => d.y)
  .attr('r', 5);

过渡最佳实践 #

1. 合理设置持续时间 #

javascript
d3.select('circle')
  .transition()
  .duration(300)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .duration(1000)
  .attr('r', 20);

2. 使用适当的缓动函数 #

javascript
d3.select('circle')
  .transition()
  .ease(d3.easeCubicOut)
  .attr('r', 20);

d3.select('circle')
  .transition()
  .ease(d3.easeBounce)
  .attr('r', 20);

3. 避免过度动画 #

javascript
d3.select('circle')
  .transition()
  .duration(250)
  .attr('r', 20);

4. 使用命名过渡管理复杂动画 #

javascript
const t1 = d3.transition('move')
  .duration(1000);

const t2 = d3.transition('color')
  .duration(500);

d3.select('circle')
  .transition(t1)
  .attr('cx', 200);

d3.select('circle')
  .transition(t2)
  .attr('fill', 'red');

过渡方法速查表 #

方法 描述 示例
transition 创建过渡 .transition()
duration 设置持续时间 .duration(1000)
delay 设置延迟 .delay(100)
ease 设置缓动函数 .ease(d3.easeLinear)
attr 属性过渡 .attr('r', 20)
style 样式过渡 .style('fill', 'red')
attrTween 属性插值 .attrTween('r', ...)
styleTween 样式插值 .styleTween('fill', ...)
on 事件监听 .on('end', ...)
interrupt 中断过渡 .interrupt()

下一步 #

现在你已经掌握了过渡动画,接下来学习 交互事件,了解如何为图表添加交互功能!

最后更新:2026-03-28