Fabric.js 动画

2018-11-27 20:22:12
2320次阅读
0个评论
一般大家提到 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
})

收藏10

登录 后评论。没有帐号? 注册 一个。