【canvas】動畫原理の胡克定律

老姚發表於2020-01-13

先看下平面拖拽效果

【canvas】動畫原理の胡克定律

還有水平拖放版本

【canvas】動畫原理の胡克定律

先說明一下,這個彈簧效果受啟發於第二本參考書。

不知道你看到後有沒有覺得很複雜,為了完成它,我大約用了一個小時左右。

本文會詳細介紹一下實現的具體原理,研究明白後,動畫的運動學基礎應該也算是過關了吧。

1. 運動學一般原理

首先從運動動畫的基本原理說起。

我們知道,根據 requestAnimationFrame 實時更新小球 ball 的座標就能實現動畫。

ball.x += 3
ball.y += 4
複製程式碼

每一幀,小球向右移動 3px,向下移動 4px。其中 3 和 4 就是速度的刻畫。因此可以把它們分別定義成水平速度和垂直速度。

vx = 3
vy = 4
ball.x += vx
ball.y += vy
複製程式碼

上面程式碼中速度是常量,因而運動是勻速直線運動。如果要加速運動,那麼就需要引入加速度:

ax = 0.5
ay = 1
vx += ax
vy += ay
ball.x += vx
ball.y += vy
複製程式碼

根據牛二定律,力與加速度成正比。

f = m * a

我們假設小球的質量是單位 1,那麼就可以把力的模型引入進來。比如小球的拋射運動很容易實現:

vx = 2.5
vy = -6
gravity = 0.1
vy += gravity
ball.x += vx
ball.y += vy
複製程式碼

效果如下:

【canvas】動畫原理の胡克定律

其中小球類的程式碼仍是常規的實現:

class Ball{
  constructor() {
    this.x = 0
    this.y = 0
    this.radius = 20
  }
  draw(context) {
    context.save()
    context.translate(this.x, this.y)
    context.fillStyle = 'red'
    context.beginPath()
    context.arc(0, 0, this.radius, 0, Math.PI * 2)
    context.fill()
    context.restore()
  }
}
複製程式碼

說到力,為了模擬真實世界物體的運動,有時我們還要考慮摩擦力。這裡我們假設運動物體的速度每一幀後都會衰減。這裡使用模型:

friction = 0.99
vx *= friction
vy *= friction
ball.x += vx
ball.y += vy
複製程式碼

每一幀速度都打會 0.99 折。0.99 看起來接近 1,但是它是指數衰減的。瀏覽器一般幀率 60 左右,因此 0.99 的 60 次方降很快降到一個接近 0 的數。

效果如下:

【canvas】動畫原理の胡克定律

2. 胡克定律

有了一般運動學原理的鋪墊後,接下來正式分析開篇動畫的原理。

其中小球除了受重力和摩擦力的影響外,還受到了彈簧的力。

高中我們都學過胡克定律:彈簧自身受到的力與彈簧的形變數成正比。

F = k * x

其中 k 是彈簧係數,由彈簧自身決定。x 是彈簧的形變數。

【canvas】動畫原理の胡克定律

跟據作用力與反作用力相等,小球受到的力為:

f = -k * x

因此水平簡諧運動的關鍵程式碼:

var x = ball.x -spring.x - initLength
var f = -k * x
vx += f
ball.x += vx
spring.length = ball.x - spring.x
複製程式碼

其中 x 是彈簧形變數。f 是小球受到的彈簧力,最後兩句程式碼是更新小球的速度和彈簧的當前長度。

這裡沒有加入摩擦力,因此它會無限運動下去。效果如下:

【canvas】動畫原理の胡克定律

為了讓小球能運動起來,初始時,把小球拉開了一段距離。這個距離也是彈簧的振幅。

var amplitude = 70
ball.x += amplitude
var vx = 0
複製程式碼

其中彈簧主要使用二次貝塞爾曲線 quadraticCurveTo 模擬的。

class Spring{
  constructor(length) {
    this.x = 0
    this.y = 0
    this.angle = 0
    this.length = length
  }
  draw(context) {
    context.save()
    context.translate(this.x, this.y)
    context.rotate(this.angle)
    context.strokeStyle = 'white'
    context.beginPath()
    context.moveTo(0, 0)
    var n = 16
    var h = 25
    var d = (this.length - 2 * h) / n
    context.lineTo(h, 0)
    for (var i = 0; i < n; i++) {
      var dir = i % 2 == 0 ? 1 : -1
      context.quadraticCurveTo(h + d * ( i + 0.5), 20 * dir,  h + d * (i + 1), 0)
    }
    context.lineTo(this.length, 0)
    context.stroke()
    context.restore()
  }
}
複製程式碼

下面分析一下開篇效果實現需要注意的細節。

3. 力的合成與分解

在上面的基礎上,加入摩擦力和拖拽的邏輯,很容易實現開篇的第二個效果。這裡不再過多解釋了。

而第一個效果稍微複雜一點。首先每一幀都要更新彈簧頭的位置。

spring.x = mouse.x
spring.y = mouse.y
複製程式碼

此時小球收到的彈簧力是:

var dx = ball.x - spring.x
var dy = ball.y - spring.y
var x = Math.sqrt(dx * dx + dy * dy) - initLength
var f = -k * x
複製程式碼

為了求出水平和垂直的加速度,此時就需要力的分解了:

【canvas】動畫原理の胡克定律

根據三角關係有:

var angle = Math.atan2(dy, dx)
var fx = f * Math.cos(angle)
var fy = f * Math.sin(angle)
複製程式碼

最後是彈簧力、重力和摩擦力合成邏輯:

vx += fx
vy += fy
vy += gravity
vx *= friction
vy *= friction
ball.x += vx
ball.y += vy
複製程式碼

另外注意一點是,在更新小球后位置後,因為小球位置變了,因此還需要計算彈簧的此時的新長度和方向。

點選檢視最終效果和完整程式碼

感謝你看到這裡,希望有所幫助。

本文完。



下面的內容是關於本系列的介紹。

2019年末,本人立了個flag,2020年要研究透canvas動畫技術。

【canvas】動畫原理の胡克定律

(圖中二維碼是我的唯一微訊號,如有掘友想加的,麻煩備註下【掘金】哈。)

在這個系列,我想寫一些常見動畫知識,本文是第4篇,篇幅可能會長短不一。更多的請檢視我的個人主頁,或者《系列目錄》

因為篇幅問題,根據以往的經驗,贊數不會太多,畢竟大家都喜歡給那種短時間看不完的文章點贊。嗯,我好像也是這樣。^_^

其實寫文章,主要還是給自己看的,算是自我進步的一個見證吧。抱著這種心態也許能好些。

另外關於canvas技術,我目前完整看完了3本書。算是過了基礎一關。

1.《HTML5 Canvas核心技術》

2.《HTML5+JavaScript動畫基礎》

3.《WebGL程式設計指南》

本系列一些文章可能會參考裡面的知識體系,對於一些屬於領域共識知識,如有區域性雷同,只能說:“自己憑本事學來的,怎能算抄襲。。。”。

開玩笑了,想法來源能提一句還是要提一句的。特別喜歡《精英日課》文章裡的一段話:

【canvas】動畫原理の胡克定律

至於文章內容,canvas的API,本系列可能不會準備逐條介紹了,還請初學的童鞋見諒哈。MDN都有的,挺詳細的。同時,文章中遇到的還是會簡單提下。主要核心是闡述一些技巧和原理層面的知識個人理解吧。另外也打算分析一些codepen上炫酷動畫的實現原理,如果有時間可能會分析幾個動畫引擎,當然都是2D的。

再次感謝你閱讀到這裡。下一篇文章見。

相關文章