Day10-D3 Transition 动画

本篇大纲:transition( ) 移动、变换颜色、transition.delay( )、transition.ease( )、transition.on( )

今天要来讲讲我觉得 D3.js 最有趣的部分啦!动画效果基本上是酷炫前端网站必不可少的功能,D3 也知道这一点,并开发出一系列处理动画效果的 API 们
https://ithelp.ithome.com.tw/upload/images/20210922/20134930hAWicuhfIX.jpg

一般主要是使用 selection.transition( ) 这个 API 来处理动画,.transition() 归类在selection 底下是因为它的设计逻辑是由d3.selection延伸而来,因此要先用select() 的方法选定 DOM 元素後,才能将动画绑定到回传的 selection 实体上。

一旦把所选的 DOM 元素加上 transition( ) 之後,就能建立d3的动画效果。特别的是,这个动画效果还多了动画执行时间动画生命周期这些特性,因此我们就可以用其他 transition( ) 旗下的 API 来调整动画的时长、动画方式、延迟时间等等。我们能使用的 API 包含:

  • transition.duration( ) ⇒ 控制动画时长
  • transition.ease( ) ⇒ 调整动画运作方式
  • transition.delay( ) ⇒ 设定动画延迟时间
  • transition.on(事件, callback) ⇒ 设定动画产生的事件

如何使用 .transition( )

transition() 的用法也非常简单,只要选定要加动画的DOM元素之後,加上.transition(),接着在後方接上想用动画完成的项目,这些项目就会被加上动画啦~这边要特别说一下,因为下一篇才会讲到d3的事件触发,所以这篇的范例我们都先用JS原生的on click事件去触发动画,下一篇开始再跟大家说要如何使用d3方便的事件触发 API 们~

移动

以下就用这个例子来示范,目前画面上[0,0]的位置有一个方块,按下移动的按钮後,这个方块会被移动到[140,60]的位置
https://ithelp.ithome.com.tw/upload/images/20210922/20134930xxTYrYnryl.jpg

// html
<svg class="move"></svg>
<button type="button" class="btn btn-primary moveBtn">移动</button>

// js
// 方块大小
const rect = d3.select('.move')
                .append('rect')
                .attr('width', 40)
                .attr('height', 40)
                .attr('stroke', 'black')
 
// 移动事件触发   
document.querySelector('.moveBtn').addEventListener('click', function(){
    rect.attr('transform', 'translate(140, 60)')
})

没有加上动画时,按下移动的按钮会发现正方型很生硬地直接跳到[140,60]的位置
https://i.imgur.com/vX2l4tk.gif

救命!!谁想要这麽生硬蛮横地跳转啊!?快点加上动画缓和一下

// 移动事件触发   
document.querySelector('.moveBtn').addEventListener('click', function(){
    rect.transition() // 加上动画
		.attr('transform', 'translate(140, 60)')
})

总算好多了~~这个就是我们要的平滑转换动画效果
https://i.imgur.com/7y1BaSL.gif

但我觉得这个动画有点太快了,想进行一些特别设定,例如:我想要设定动画的时长是五秒钟!这时就可以派出 .duration( ) 啦~

// 移动事件触发   
document.querySelector('.moveBtn').addEventListener('click', function(){
    rect.transition()
        .duration(5000) // 设定动画时间持续5秒钟
		.attr('transform', 'translate(140, 60)')
})

完成的效果就像这样
https://i.imgur.com/ViatEns.gif

变换颜色

除了移动之外,颜色的变化、边框粗细等等也都可以用动画来调整

// html
<svg class="changeColor"></svg>
<br>
<button type="button" class="btn btn-primary changeBtn">改变颜色</button>

// 改变颜色
const round = d3.select('.changeColor')
                .append('circle')
                .attr('cx', 100)
                .attr('cy', 50)
                .attr('r', 25)
                .attr('fill', 'orange')
                .attr('stroke-width', '0.5px')
                .attr('stroke', 'black')

document.querySelector('.changeBtn').addEventListener('click', function(){
    round.transition()
         .duration(1000)
         .attr('fill', 'green')
         .attr('stroke-width', '6px')
         .attr('stroke', 'red')
})

https://i.imgur.com/ZtT35Lg.gif

大功告成~transition( ) 是不是很简单呀?但要特别注意的是,想加上动画的项目一定要放在 transition( ) 之後,如果放在 transition( ) 之前的话,就无法绑定动画效果

.delay( ) 动画延迟

我自己觉得 transition.delay( ) 这个方法很有趣,它的参数是填入想秒数,但也可以填入方法去各别设定每个元素的延迟秒数,达到元素一个接一个进行动画的效果,就像这样:
https://i.imgur.com/a0R0qPx.gif

那具体该怎麽做呢?话不多说直接上程序码吧!

一开始一样先设定我们的资料,并把资料绑定到 DOM 上

// html
<svg class="delay"></svg>
<button type="button" class="btn btn-primary mt-3 delayBtn">delay开始</button>

// js
const dataDelay = [160, 140, 120, 100, 80, 60 ,40 ,20]
const delay = d3.select('.delay')
                .selectAll('circle')
                .data(dataDelay)
                .enter()
                .append('circle')
                .attr('cx', d => d)
                .attr('cy', 30)
                .attr('r', 15)
                .attr('fill', 'blue')
                .attr('opacity', '0.5')

接着设定按下按钮时触发的动画事件,并且设定 .delay( ) 要一一进行

document.querySelector('.delayBtn').addEventListener('click', function(){
  delay.transition()
       .delay((d,i)=> i*200) // 分别延迟
       .attr('cx', d => d+120) // 位移距离

})

这样就完成啦~~

.ease( ) 动画效果

我们接着看到另外一个有趣的API,transition.ease( ) 的参数必须是一个方法,用来设定动画每一帧的时长,藉此达到不同的动画效果。但设定动画运作方式其实蛮复杂的,还好 D3.js 已经帮我们设定好多种不同的动画方法了!这些方法有:

  • d3.easeBack、d3.easeBackIn、d3.easeBackInOut、d3.easeBackOut
  • d3.easeBounce、d3.easeBounceIn、d3.easeBounceInOut、d3.easeBounceOut
  • d3.easeCircle、d3.easeCircleIn、d3.easeCircleInOut、d3.easeCircleOut
  • d3.easeCubic、d3.easeCubicIn、d3.easeCubicInOut、d3.easeCubicOut
  • d3.easeElastic、d3.easeElasticIn、d3.easeElasticInOut、d3.easeElasticOut
  • d3.easeExp、d3.easeExpIn、d3.easeExpInOut、d3.easeExpOut
  • d3.easeLinear
  • d3.easePoly、d3.easePolyIn、d3.easePolyInOut、d3.easePolyOut
  • d3.easeQuad、d3.easeQuadIn、d3.easeQuadInOut、d3.easeQuadOut
  • d3.easeSin、d3.easeSinIn、d3.easeSinInOut、d3.easeSinOut

我们只要找到想要的方法并带入就可以~想更深入了解这些方法的人可以看官网对於每个方法的的详细解说

但光看官方文件实在很难理解这些动画效果,所以我们直接用画面来展示一下吧!首先,一样先在画面上画个圆

// html
<svg class="ease"></svg>
<select name="ease" id="ease">
    <option value=""></option>
</select>
<button type="button" class="btn btn-primary mt-3 easeBtn" onClick="updateEast();">Ease开始</button>

// js
const easeDot = d3.select('.ease')
                  .append('circle')
                  .attr('cx', 40)
                  .attr('cy', 40)
                  .attr('r', 30)
                  .attr('fill', 'blue')

接着,我们把d3所有的API 叫出来,并抓出属於 ease 的 API 带入< option > 中让我们能够选取想要的动画效果

const easeNames = Object.keys(d3).filter(d=>{
        return d.slice(0,4) === 'ease' // 抓出所有方法中,名称内有ease的方法
})

console.log(easeNames)

d3.select('#ease')
  .selectAll('option')
  .data(easeNames)
  .join('option')
  .attr('value', function(d) { return d; })
  .text(function(d) { return 'd3.' + d; });

最後,我们设定按下按钮时,要运行目前选到的动画效果

function updateEast(){
    let easeName = d3.select('#ease').node().value;
    console.log(easeName)
    easeDot.attr('cx', 40) // 回原点
           .transition()
           .ease(d3[easeName]) // 设定动画效果
           .attr('cx', 200)
}

完成!!我们来玩玩看吧~
https://i.imgur.com/17RlnCx.gif

transition.on( ) 动画的生命周期

前面示范的动画效果都是被特定指令触发止後只执行一次,但如果想制作重复执行、不停循环的动画 该怎麽办呢?这时我们就可以利用 transition.on( ) 产生的四个事件来进行设计。

transition.on( ) 会产生四种事件:

  • start ⇒ 动画开始时
  • end ⇒ 动画结束时
  • interrupt ⇒ 动画被中断
  • cancel ⇒ 动画被取消

我们使用 start 这个事件来制作一个无限循环的动画。 首先,一样先在画面上建立一个蓝色圆点点

// html
<svg class="loopAnimation"></svg>

//js
const loop = d3.select('.loopAnimation')
                .append('circle')
                .attr('cx', 50)
                .attr('cy', 50)
                .attr('r', 25)
                .attr('fill', 'blue')

接着,将这个圆点加上transition.on( ),并设定要触发的动画事件为"start",它的callback function 叫做 goRight。这边的意思就是:每当动画开始时,我要执行 goRight 这个方法

const loop = d3.select('.loopAnimation')
                .append('circle')
                .attr('cx', 50)
                .attr('cy', 50)
                .attr('r', 25)
                .attr('fill', 'blue') 
                .transition()
                .duration(2000)
                .on('start', goRight)

然後我们来设定 goRight 方法吧!

  • 一开始,先用 d3.active(this) 抓到要进行动画的 DOM 元素(也就是loop这个圆点)
  • 接着,设定原点要移动到 200的位置
  • 最後,一样加上transition.on( ),并设定要触发的动画事件为"start",它的callback function 叫做 goLeft
function goRight(){
    d3.active(this)
	  .attr('cx', 200)
      .transition()
      .on("start", goLeft);
}

接着我们一样来设定 goLeft 方法

  • 一开始一样用 d3.active(this) 抓到要进行动画的 DOM 元素(也就是loop这个圆点)
  • 再来设定原点要移动回到 50的位置
  • 最後,一样加上transition.on( ),并设定要触发的动画事件为"start",它的callback function 则是到刚刚设定的 goRight 方法
function goLeft (){
    d3.active(this)
      .attr('cx', 50)
      .transition()
      .on("start", goRight)
}

我们利用 transition() start 的事件,设定了 goRight、goLeft 两个方法,并让这两个方法互相呼叫,这样一来就能成功做出无限循环的动画啦!

https://i.imgur.com/YWHqGlE.gif

完整范例

看完上面的例子,是不是觉得d3.transition( ) 很有趣呢?最後我们实际示范一个常见的图表动画:当资料变动时,图表要搭配动画去改变颜色跟高度吧!

// html
<h5  class="mt-5">3.完整图表动画</h5>
<div class="chartContainer"></div>
<button type="button" id="start" class="btn btn-primary">动画开始</button>

//js
//定义两个资料,都是Y值,x值就用阵列索引即可
    let data1 = [150, 122, 133, 161, 116, 139, 143, 115, 193, 137, 122, 141];
    let data2 = [180, 146, 180, 172, 133, 149, 152, 138, 188, 192, 117, 146];
    let n = data1.length, //资料点的数量
        mx = d3.max(d3.merge([data1, data2])) //抓两个阵列的最大值

    // svg
    const svg = d3.select('.chartContainer').append('svg');
    svg.attr('width', 500)
        .attr('height', 300);

    svg.selectAll('rect')
        .data(data1)
        .enter()
        .append('rect')
        .attr('x', 0)
        .attr('y', (d, i) => i * 30)
        .attr('width', (d, i) => d)
        .attr('height', 20)
        .attr('fill', 'orange')

    // 动画开始
    d3.select('#start')
      .on('click', function () {
        svg.selectAll('rect')
            .data(data2) // 资料变化
            .transition() // 加上动画
            .duration(1000) //点击之後每个 bar 会在1秒内到达更新位置
            .delay((d, i) => 200 * i) //每个bar在分别delay後才开始动画,
            // delay 时间乘上index,做出从上至下更新的动画效果
            .attr('width', (d, i) => d)
            .attr('fill', '#66f9ff')
    });

实际呈现是这样子
https://i.imgur.com/gx0KgFP.gif

这就是 d3.js 的动画效果啦~有兴趣的人也可以自己做一次看看!明天我们就要进入另一个有趣的领域罗~


Github Page 图表与 Github 程序码

这边附上本章的程序码与图表 GithubGithub,需要的人请自行取用~


<<:  Day 7 - Function 时空旅行 (2) - 拆解与命名

>>:  【Day06】生命周期 Lifecycle(Class Component)

如何透过SEO搜寻优化布局全球市场

在辅导客户中多数都是外销公司的辅导顾问案件,尤於外销市场不同於内销市场在操作 业务开发 的确有难度。...

中阶魔法 - 提升 Hoisting

前情提要 艾草:「我们今天来提升一下吧!」 「不是每天都在提升魔力总量了吗?」 艾草:「不一样唷,今...

{Day28}Biometric

Biometric Biometric让用户不必每次打开APP时都记住帐户用户名和密码,只需使用生物...

Day 28|Divi 功能练习 20 Contact Form Module 联络表单设计

嗨呦大家好我是 Jasmine~脑袋总是胡思乱想停不下来的设计师一枚\(✪ω✪)/ 来到连假最後一天...

容器化及容器技术(containerization and container technology)

-虚拟机和容器部署(来源:NIST SP 800-190) 虚拟机器监视器(Hypervisor)...