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