【canvas】动画原理の胡克定律

11,521 阅读5分钟

先看下平面拖拽效果

还有水平拖放版本

先说明一下,这个弹簧效果受启发于第二本参考书。

不知道你看到后有没有觉得很复杂,为了完成它,我大约用了一个小时左右。

本文会详细介绍一下实现的具体原理,研究明白后,动画的运动学基础应该也算是过关了吧。

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

效果如下:

其中小球类的代码仍是常规的实现:

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 的数。

效果如下:

2. 胡克定律

有了一般运动学原理的铺垫后,接下来正式分析开篇动画的原理。

其中小球除了受重力和摩擦力的影响外,还受到了弹簧的力。

高中我们都学过胡克定律:弹簧自身受到的力与弹簧的形变量成正比。

F = k * x

其中 k 是弹簧系数,由弹簧自身决定。x 是弹簧的形变量。

跟据作用力与反作用力相等,小球受到的力为:

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 是小球受到的弹簧力,最后两句代码是更新小球的速度和弹簧的当前长度。

这里没有加入摩擦力,因此它会无限运动下去。效果如下:

为了让小球能运动起来,初始时,把小球拉开了一段距离。这个距离也是弹簧的振幅。

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

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

根据三角关系有:

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动画技术。

(图中二维码是我的唯一微信号,如有掘友想加的,麻烦备注下【掘金】哈。)

在这个系列,我想写一些常见动画知识,本文是第4篇,篇幅可能会长短不一。更多的请查看我的个人主页,或者《系列目录》

因为篇幅问题,根据以往的经验,赞数不会太多,毕竟大家都喜欢给那种短时间看不完的文章点赞。嗯,我好像也是这样。^_^

其实写文章,主要还是给自己看的,算是自我进步的一个见证吧。抱着这种心态也许能好些。

另外关于canvas技术,我目前完整看完了3本书。算是过了基础一关。

1.《HTML5 Canvas核心技术》

2.《HTML5+JavaScript动画基础》

3.《WebGL编程指南》

本系列一些文章可能会参考里面的知识体系,对于一些属于领域共识知识,如有局部雷同,只能说:“自己凭本事学来的,怎能算抄袭。。。”。

开玩笑了,想法来源能提一句还是要提一句的。特别喜欢《精英日课》文章里的一段话:

至于文章内容,canvas的API,本系列可能不会准备逐条介绍了,还请初学的童鞋见谅哈。MDN都有的,挺详细的。同时,文章中遇到的还是会简单提下。主要核心是阐述一些技巧和原理层面的知识个人理解吧。另外也打算分析一些codepen上炫酷动画的实现原理,如果有时间可能会分析几个动画引擎,当然都是2D的。

再次感谢你阅读到这里。下一篇文章见。