iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 9
2
Modern Web

Fabricjs 筆記系列 第 9

Day 9 - Fabricjs 動畫

  • 分享至 

  • xImage
  •  

一般大家提到 canvas 一定會想到一些酷炫的動畫效果,Fabricjs 當然也少不了這些啦!

Fabricjs 提供了簡易的動畫 Api 讓我們可以做一些簡單的動畫效果,透過操作我們自己所新增的物件,讓物件動起來,今天就讓我們一起來玩玩 Fabricjs 提供的動畫吧。

Fabricjs 所有的物件上都能使用 animate 這個方法,就如同 set 這個常用的方法一樣,這邊馬上來試試。

object.animate

// 動畫練習-角度轉換
rect.animate('angle', 360, {
  onChange: canvas.renderAll.bind(canvas)
})

可以看到我們簡單的透過 animate() 這個方法就輕鬆的讓矩形旋轉 360 度,我們稍微看一下帶入的參數:

  • 第一個參數 'angle' 是想要被改變的物件屬性,可以是:angle、top、left (缺點是我們沒辦法做出顏色漸變的動畫)
  • 第二個參數 360 就是改變的目標值,也可使用 '+=XXX' 來設定要增加或減少多少值
  • 第三個參數 {} 傳入物件,這邊是 animate 各種細部的設定,如 duration、動畫時間效果、callbacks

onChange

為什麼我們要在第三個參數中加入 onChange 呢?這是因為在我們呼叫了 animate 方法後,canvas 會一直更新物件的狀態,angle 會慢慢的從 0~360,所以我們必須要在每個影格都重新繪製物件,讓畫面才有動畫的感覺。

duration 動畫執行時間

我們在第三個參數內可以設定動畫執行的經過時間要多久。

rect.animate('angle', 360, {
  duration: 3000, // 三秒才完成動畫
  onChange: canvas.renderAll.bind(canvas)
})

結果

easing 動畫效果

可以在 animate 第三個參數內加入 easing 來變更動畫進行時的效果,這邊可用 fabric.util.ease 所提供的一些效果。

rect.animate('angle', 360, {
  duration: 1000,
  onChange: canvas.renderAll.bind(canvas),
  easing: fabric.util.ease.easeInOutBack
})

可以看到我們加上了 easing: fabric.util.ease.easeInOutBack 後,呈現了不一樣的動畫效果。

這邊 fabric.util.ease 還提供了更多有趣的動畫效果
fabricjs doc - http://fabricjs.com/docs/fabric.util.ease.html

累加數值

我們也可以透過相對位置累加的方式,來做動畫移動的效果。

  • '+=500'
  • '-=500'
// 動畫練習-角度轉換
rect.animate('left', '+=500', {
  onChange: canvas.renderAll.bind(canvas)
})

組合動畫效果

可以把動畫效果合在一起,做出更豐富的效果
這邊結合上面兩個動畫

  • 移動
  • 旋轉

實作練習

控制 100 顆球球的位置、大小、透明度。

設定畫布和操控動畫的變數

首先我們要產生靜態的 canvas 因為我們不需要去操作他們,並且定義一個 playing 的 Boolean 型態變數方便我們之後去操控動畫的動或不動。

// 視窗大小
const windowSize = {
  width: window.innerWidth,
  height: window.innerHeight
}

// 產生靜態 canvas
const canvas = new fabric.StaticCanvas('canvas', {
  height: windowSize.height, // 讓畫布同視窗大小
  width: windowSize.width
})

let playing = true // 預設開啟

產生亂數

我們需要一個方便產生亂數的函數,讓我們動畫更加有變化性

// 取得亂數
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

產生 100 個圓形並設定動畫

使用迴圈產生 100 個圓形,並且為他們設定動畫,這邊我把動畫放在另外一個函數,須把 circle 物件傳入 setAnimate

這邊有一個重點是必須要記錄哪個圓形是最後被產生的,方便我們之後重整畫布。

circle.lastAdd = i === 99 這邊

// add 100 circle
for (let i = 0; i<100; i++) {
  // 產生圓形,並給定隨機起始值
  let circle = new fabric.Circle({
    radius: getRandomInt(2, 15),
    left: getRandomInt(0, windowSize.width),
    top: getRandomInt(0, windowSize.height),
    opacity: getRandomInt(0.1, 1)
  })
  // 紀錄一下自己是第幾個被產生的 circle
  circle.lastAdd = i === 99
  canvas.add(circle)
  // 設定動畫
  playing && setAnimate(circle)
}

setAnimate(circle)

這邊就是最重要的設定動畫的函數啦!
這邊分別設定了以下四個動畫效果,結合剛剛的 getRandomInt,讓動畫更加隨機更有趣!

  • radius
  • opacity
  • left
  • top
    完整函數
// 設定動畫函數
function setAnimate (circle) {
  // 變化半徑
  circle.animate('radius', getRandomInt(2, 15), {
    duration: getRandomInt(1000, 5000)
  })
  // 變化透明度
  circle.animate('opacity', getRandomInt(0, 1), {
    duration: getRandomInt(1000, 5000)
  })
  // 變化座標
  circle.animate('left', getRandomInt(0, windowSize.width), {
    easing: fabric.util.ease.easeInOutCubic,
    duration: getRandomInt(1000, 5000)
  })
  // 變化座標
  circle.animate('top', getRandomInt(0, windowSize.height), {
    onChange: () => {
      // 不需要每個 circle 都呼叫 canvas.renderAll()
      // 只有最後一個被新增的物件 onChange 去更新畫布
      if (circle.lastAdd) canvas.renderAll()
    },
    onComplete: () => playing && setAnimate(circle),
    easing: fabric.util.ease.easeInOutCubic,
    duration: getRandomInt(1000, 5000)
  })
}

重點在最後一個動畫設置,幫大家拿出來看一下。

circle.animate('top', getRandomInt(0, windowSize.height), {
    onChange: () => {
      // 不需要每個 circle 都呼叫 canvas.renderAll()
      // 只有最後一個被新增的物件 onChange 去更新畫布
      if (circle.lastAdd) canvas.renderAll()
    },
    onComplete: () => playing && setAnimate(circle),
    easing: fabric.util.ease.easeInOutCubic,
    duration: getRandomInt(1000, 5000)
  })

我們只需要在最後一個 circle.animate 設定動畫來加入 onChange 以及 onComplete 函數,不然會重複設置 4 次,造成動畫的效能降低。

我們再來一一的看一下他們發生了什麼。

onChange

這邊需要判斷是否為最後一個被新增的 circle 物件,否則會重複呼叫 canvas.renderAll() 這個重整函數。(100 個物件就會重複呼叫 99 次),造成效能變差。

onComplete

這邊很直覺的就是當我們 circle 物件動畫做完後就繼續做新的一次動畫,讓我們看起來動畫是連續的。

暫停、繼續按鈕

透過一開始設置的 playing 變數,來控制動畫是否繼續,canvas.getObjects() 抓取所有在 canvas 之下的所有 circle 物件。透過 forEach(),在一次地將所有 circle 物件加入動畫效果。

// 左上方按鈕
document.querySelector('#toggle').addEventListener('click', (e) => {
  const targetEl = e.target
  if (playing) {
    targetEl.innerHTML = 'start'
  } else {
    targetEl.innerHTML = 'stop'
    // 讓所有物件在一次動起來
    canvas.getObjects().forEach(circle => setAnimate(circle))
  }
  playing = !playing
})

完整程式碼 - https://codepen.io/nono1526/pen/BqqBxo

今日小結

簡單練習了 fabricjs 動畫的使用
今日 codepen 連結


上一篇
Day 8 - Fabricjs 事件
下一篇
Day 10 - 為圖形填入漸層色
系列文
Fabricjs 筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
advancedor96
iT邦新手 5 級 ‧ 2018-10-24 08:53:41

細讀看完,這一篇寫得真好!變數命名也很有意義、有直覺到。

Nono iT邦新手 5 級 ‧ 2018-10-24 23:43:46 檢舉

感謝大大這麼用心地看完/images/emoticon/emoticon41.gif

我要留言

立即登入留言