过渡与动画
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 而非 style | SVG 属性过渡比 CSS 样式过渡性能更好 |
避免过渡 d 属性 | 路径插值计算量大,优先过渡 transform |
| 使用 CSS transition 替代 | 简单属性变化用 CSS 更高效 |
| 及时 interrupt | 新数据到来时中断旧过渡,避免队列堆积 |
过渡不是万能的
对于高频更新(如实时数据流 > 10fps),不要使用 D3 transition。直接设置属性或使用 requestAnimationFrame 手动控制。
7. 与 CSS 动画的对比
| 维度 | D3 Transition | CSS Transition/Animation |
|---|---|---|
| 控制粒度 | 逐元素、逐属性 | 类级别 |
| 数据驱动 | 天然支持 | 需手动映射 |
| 中断/链式 | 完整 API | 有限 |
| 性能 | JS 驱动,较慢 | GPU 加速,更快 |
| 适用场景 | 数据更新动画 | 交互状态动画(hover) |
推荐策略:交互反馈(hover/active)用 CSS,数据变化动画用 D3 transition。