<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;
// 首先将部分属性抽离出 外层的svg bounds 已经我们后续要做出改变的rect mean x轴标签 用于后续获取 
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)`)
  // bins 用来包裹bin
  bounds.append('g').attr('class', 'bins')
  // 均线标签
  bounds.append('line').attr('class', 'mean')
  // x轴标签
  bounds.append('text').attr('class', 'x-axis-label')
  // x轴包裹标签
  const xAxis = bounds.append('g')
  // 内容绘制 将我们绘图的属性,和后续要用的selection传入
  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)

d3.transition

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更改进行链化或同步时。
  • 分类:
    前端
    标签: