<animate> 是一个可以在svg内部定义的的原生元素。
<svg width="120" height="120">
<rect x="10" y="10" width="100" height="100" fill="cornflowerblue">
<animate attributeName="x" values="0;20;0" dur="2s" repeatCount="indefinite" />
<animate attributeName="fill" values="cornflowerBlue;maroon;cornflowerBlue" dur="6s" repeatCount="indefinite" />
</rect>
但是它不支持ie,而且是静态动画,无法传入参数。
在vue下各个属性都可以通过绑定来动态传入参数。
<rect x="10" y="10" width="100" height="100" fill="cornflowerblue">
<animate :attributeName="direct" values="0;20;0" dur="2s" repeatCount="indefinite" />
<animate attributeName="fill" :values="colors" dur="6s" repeatCount="indefinite" />
</rect>
<script setup lang="ts">
import { ref } from 'vue'
const direct = ref('x')
const colors = ref('red;blue;red')
setTimeout(() => {
direct.value = 'y'
colors.value = 'cornflowerBlue;maroon;cornflowerBlue'
}, 5000)
</script>
css3 transitions
css3 transitions学习
现在我们想有如下效果:
数据切换时柱状图rect高度动态变化
给rect添加过渡属性
#deom-bar-chart1 {
.bin rect {
transition: height 1s ease-out, y 1s ease-out;
const initChart = () => {
const metricArr = [
'windSpeed',
'moonPhase',
'dewPoint',
'humidity',
'uvIndex',
'windBearing',
'temperatureMin',
'temperatureMax',
const svg = d3.select('#deom-bar-chart1').append('svg').attr('viewBox', dimensions.viewBox)
const bounds = svg.append('g').style('transform', `translate(${dimensions.margin.left}px,${dimensions.margin.top}px)`)
bounds.append('g').attr('class', 'bins')
bounds.append('line').attr('class', 'mean')
bounds.append('text').attr('class', 'x-axis-label')
const xAxis = bounds.append('g')
drawHistogram(metricArr[0], svg, bounds, xAxis)
let i = 0
setInterval(() => {
i = (i + 1) % metricArr.length
drawHistogram(metricArr[i], svg, bounds, xAxis)
}, 2000)
bin切换
const bins = binsGenerator(dataset)
const yAccessor = (d: []): number => d.length
const yScale = d3
.scaleLinear()
.domain([0, d3.max(bins, yAccessor)])
.range([dimensions.boundsHeight, 0])
.nice()
// 选取bins后通过data方法数据绑定
let binsGroups = bounds.select('.bins').selectAll('.bin').data(bins)
// 使用remove方法移除exit(即已经存在的旧bin)
const oldBins = binsGroups.exit()
oldBins.remove()
// 通过enter添加新的g标签和其下数据
const newBinGroups = binsGroups.enter().append('g').attr('class', 'bin')
newBinGroups.append('rect')
newBinGroups.append('text')
const barPadding = 1 // 柱状图间隔
// 通过merge方法将新的数据merge到_groups
binsGroups = binsGroups.merge(newBinGroups as any)
// 数据属性填充
const barRect = binsGroups
.select('rect')
.attr('x', (d: any) => xScale(d.x0))
.attr('y', (d: any) => yScale(d.length))
.attr('width', (d: any): number => d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]))
.attr('height', (d: any) => dimensions.boundsHeight - yScale(d.length))
.style('fill', '#cccccc')
const barText = binsGroups
.select('text')
.attr('x', (d: any) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
.attr('y', (d: any) => yScale(d.length) - 5)
.style('text-anchor', 'middle')
.text(yAccessor)
CSS转换可以支持简单的属性更改,但对于更复杂的动画,我们需要使用d3-transition²⁶模块中的d3.transition()。我们什么时候想使用d3.transition()而不是CSS转换?
当我们想要确保多个动画排列起来时
当我们想在动画结束时做一些事情时(例如开始另一个动画)
当我们想要动画化的属性不是一个CSS属性时(还记得当我们试图动画化我们的bar的高度,但不得不使用转换来代替吗?d3.translate可以为非css属性更改设置动画。)
当我们想要同步添加和删除元素与动画时
当我们可能会在过渡的中途中断一段时间时
当我们想要一个自定义动画时(例如,我们可以编写一个自定义插值器来更改逐个添加新字母的文本)
先上效果图
现在开始我们不会在添加任何css来设置动画,所有动画将有d3完成!
const barRect = binsGroups
.select('rect')
.attr('x', (d: any) => xScale(d.x0))
.attr('y', (d: any) => yScale(d.length))
.attr('width', (d: any): number => d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]))
.attr('height', (d: any) => dimensions.boundsHeight - yScale(d.length))
.style('fill', '#cccccc')
console.log(barRect)
首先我们打印一下当前的rect selection
然后添加transition方法后
const barRect = binsGroups
.select('rect')
.transition()
.attr('x', (d: any) => xScale(d.x0))
.attr('y', (d: any) => yScale(d.length))
.attr('width', (d: any): number => d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]))
.attr('height', (d: any) => dimensions.boundsHeight - yScale(d.length))
.style('fill', '#cccccc')
我们可以看到selection对象变成了transition对象,他们有相同_groups和_parents属性,transition多了_id和_name属性,这是表面的改变。
让我们展开transition的__proto__属性
__proto__是JavaScript对象的原生属性,它公开此特定对象继承的方法和值。如果您不熟悉JavaScript原型链并想了解,MDN文档是一个很好的开始。
在这种情况下,我们可以看到__proto__属性包含特定于d3的方法,而嵌套的__proto__对象包含原生对象方法,如toString()
我们可以看到很多方法是从d3 selection 继承的,但是大部分都被新的transition方法重写了。
现在看bars的显示会发现有了动画效果,任何在.transition()后被调用的attr()都会使用transition的 .attr()
方法。它会在新旧值之间进行插值。
我们的图像看起来就像在左上角飞出来的。
我们希望新添加的rect从正确位置开始,初始高度为0,然后从x轴生长起来。
让我们用绿色来表示新添加的bar,使用style() 内联样式来覆盖css文件中的样式。
使用.duration(600)设置动画的持续时间。
还记得我们如何用.enter()来隔离新的数据点吗?让我们找到我们要添加新的<rect>并设置其初始值。我们希望它们从正确的水平位置开始,但是有0像素高,这样我们就可以使它们从x轴“生长”成动画。
newBinGroups
.select('rect')
.attr('x', (d: any) => xScale(d.x0))
.attr('y', dimensions.boundsHeight)
.attr('width', (d: any): number => d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]))
.attr('height', 0)
.style('fill', 'yellowgreen')
newBinGroups
.select('text')
.attr('x', (d: any) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
.attr('y', dimensions.boundsHeight - 5)
设置好初始值后让我们来设置渐变
const barRect = binsGroups
.select('rect')
.style('fill', 'yellowgreen')
.transition()
.duration(600)
.attr('x', (d: any) => xScale(d.x0))
.attr('y', (d: any) => yScale(d.length))
.attr('width', (d: any): number => d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]))
.attr('height', (d: any) => dimensions.boundsHeight - yScale(d.length))
.transition()
.style('fill', 'cornflowerblue')
console.log(barRect)
const barText = binsGroups
.select('text')
.transition()
.duration(600)
.attr('x', (d: any) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
.attr('y', (d: any) => yScale(d.length) - 5)
.style('text-anchor', 'middle')
.text(yAccessor)
后面的transition将在前面的transition动画结束后生效。
d3-ease 用来控制动画过程,看看文档,它能让你的动画更炫酷。
bar和text需要使用相同的transition同步动画,那是否可以将动画抽离出来?
当然可以:
selection下的transition可以接收一个d3.transition对象
通过调用d3.transition(),我们可以生成一个在根元素上的transiton,它可以在多个地方使用。让我们创建一个根转换——我们需要将此定义放在我们现有的转换之上,例如在我们定义bar填充之后。让我们还将其记录到控制台上,以便更近的观看。
const updateTransition = d3.transition().duration(600).ease(d3.easeBounceOut)
const barRect = binsGroups
.select('rect')
.style('fill', 'yellowgreen')
.transition(updateTransition)
.attr('x', (d: any) => xScale(d.x0))
.attr('y', (d: any) => yScale(d.length))
.attr('width', (d: any): number => d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]))
.attr('height', (d: any) => dimensions.boundsHeight - yScale(d.length))
.transition()
.style('fill', 'cornflowerblue')
console.log(barRect)
const barText = binsGroups
.select('text')
.transition(updateTransition)
.attr('x', (d: any) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
.attr('y', (d: any) => yScale(d.length) - 5)
.style('text-anchor', 'middle')
.text(yAccessor)
bar 消失的动画
首先选择要消失的元素
然后将颜色变红,再执行transition()修改y坐标和高度。
const oldBinGroups = binsGroups.exit()
oldBinGroups
.selectAll('rect')
.style('fill', 'red')
.transition(exitTransition)
.attr('y', dimensions.boundsHeight)
.attr('height', 0)
oldBinGroups.selectAll('text').transition(exitTransition).attr('y', dimensions.boundsHeight)
oldBinGroups.transition(exitTransition).remove()
这时候我们发现老bar消失和新bar变化的动画是同时发生,这就很别扭了,我们下一步让老bar消失后新bar再开始变化。
以下代码即可实现此效果,updateTransition会在exitTransition结束后开始,我们也可以使用.delay()来延迟update动画。
const exitTransition = d3.transition().duration(600)
const updateTransition = exitTransition.transition().duration(600)
最后总结:
svg <animate> 适用于静态动画。
CSS transition 适用于css的属性改变,可用于风格优化--这样我们就可以在样式表中保持更简单的过渡,主要目的是使我们的可视化感觉更流畅。
d3.transition()是我们想要用于更复杂的动画的东西:每当我们需要与另一个过渡或DOM更改进行链化或同步时。