iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

三十天成為D3.js v7 好手系列 第 10

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,需要的人請自行取用~


上一篇
Day9-D3繪圖:繪製形狀的Helper Functions
下一篇
Day11-D3 Mouse Event 滑鼠事件
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言