為何使用Canvas內元素動畫總是在顫抖?

明非發表於2018-04-24

背景

過年的專案中遇到一個問題讓我百思不得其解,明明我的幀率保持在60幀,為何我的動畫卻一直抖動?

我的場景是一個勻速直線運動的物體。

先上一個 Demo

在這個 Demo 中,小姐姐是按照 x 軸 10px/s,y 軸 30 px/s 進行移動的,不過她的移動是明顯伴隨著抖動的。

這到底是怎麼了呢?

解決

如果小姐姐的y軸速度是10px/s,我們的幀率是60f/s,計算一下:

10 / 60 = 1/6 (px/f)

實際上,的實際速度是每 6 幀才會移動 1px,這當然會有抖動,小姐姐走一步停一會,當然會抖嘍~

我索性把小姐姐的移動速度調快,調成 100px/s,發現,還是會抖動,以為高高興興能解決了這個問題,發現還是沒那麼簡單。

既然我們能算,那我們就算一算

100 / 60 = 10/6 (px/f) = 1.666666....(px/f)

寫了個for迴圈,看看一秒中每一幀小姐姐都在什麼位置

for(let i = 0; i < 60; i ++) {
  console.log(i*10/6)
}

輸出結果是這樣的:

0 1.66 3.33 4.99 6.66 8.33 9.99 11.66 13.33 14.99 16.66 18.33 19.99 21.66 23.33 24.99 26.66 28.33 29.99 31.66
33.33 34.99 36.66 38.33 39.99 41.66 43.33 44.99 46.66 48.33 49.99 51.66 53.33 54.99 56.66 58.33 59.99 61.66 63.33 64.99
66.66 68.33 69.99 71.66 73.33 74.99 76.66 78.33 79.99 81.66 83.33 84.99 86.66 88.33 89.99 91.66 93.33 94.99 96.66 98.33

那麼作為小數,Canvas 將如何定位呢?

我們來寫一個 Demo

使用 Chrome 開啟,作為一個畫素眼,我發現,小姐姐定位在 50.6px 的時候,其實就已經被渲染到 51px 的位置。

所以在Chrome中,應該使用了 Banker`s rounding 來處理小數的問題。

所以真正的位置其實是

 0 2 3 5 7 8 10 12 13 15 17 18 20 22 23 25 27 28 30 32
 33 35 37 38 40 42 43 45 47 48 50 52 53 55 57 58 60 62 63 65
 67 68 70 72 73 75 77 78 80 82 83 85 87 88 90 92 93 95 97 98

從數值來看,每幀移動的距離可能是 1px 也可能是 2px,小姐姐可能是在邊跳芭蕾邊走路嘍~

既然這樣,60 幀的幀率下,設定 60px/s 就可以解決問題了,嘗試了一下,真的可以!

總結

前端動畫/遊戲開發 requestAnimationFrame 之 鎖幀 這篇文章介紹過,在專案中我們可能對動畫進行鎖幀,幀率可能是 60 或者 30,如果我們想保證渲染不抖動,在勻速直線運動中,我們儘量保證我們設定的速度要是幀率的倍數,或者保證平均每幀移動的畫素點是一樣的。


相關文章