前言
在掘金上看到一个帖子https://w3ctrain.com/2018/08/20/trigonometry-you-must-know/
运用canvas和三角函数写了一个动画 好厉害的感觉 所以赶紧学习一下
先看效果
首先绘制头部
drawHead(t) {
ctx.save()
ctx.beginPath()
ctx.translate(0, Math.sin(t) * 4)
ctx.arc(80, -35, 35, 0, 2 * Math.PI)
ctx.fill()
ctx.closePath()
ctx.restore()
}
我会给每个方法传入周期参数 t, t 从 0 到 2 PI , 这样能保证所有的周期运动时间同步。
身体和阴影的绘制都差不多,直接跳过看脚步动画。
脚有两只,按道理应该是抬脚到落脚的动作完成时,其他部位都完成了一个完整的周期,所以在绘制脚的时候,t 需要除以 2。然后第一只脚和第二只脚相差半个脚自身的周期,可以直接将 t 替换成 t + Math.PI 就是第二脚的动画。
drawFeet (t) {
t = t / 2
ctx.translate(Math.cos(t) * -50, 0)
// 另一只脚
ctx.translate(Math.cos(t + Math.PI) * -50, 0)
}
脚步动画自身周期的一半是在地面上的,可以通过判断一下 sin 值,小于 0 则不做 y 纵轴方向上的变化。
ctx.translate(Math.cos(t) * -50, Math.sin(t) > 0 ? Math.sin(t) * -35 : 0)
还没完,为了让脚更加逼真,同样在前半个周期做一下 rotate 。
if (t < Math.PI) {
ctx.rotate(Math.sin(t) * Math.PI / 180 * -5)
}
最终得到的效果是这样的:
文章参考:https://w3ctrain.com/2018/08/20/trigonometry-you-must-know/
//完整代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
background: #f1513b;
}
</style>
</head>
<body>
<!--<canvas id="ctx" width="" height=""></canvas>-->
</body>
<script type="text/javascript">
// inspired by the pure css sad man https://codepen.io/knyttneve/pen/yqwXyM
const canvas = {
init() {
this.ele = document.createElement('canvas')
document.body.appendChild(this.ele)
this.resize()
window.addEventListener('resize', () => this.resize(), false)//resize:widow窗口大小发生变化的函数
this.ctx = this.ele.getContext('2d')
return this.ctx
},
onResize(callback) {
this.resizeCallback = callback
},
resize() {
this.width = this.ele.width = window.innerWidth * 2
this.height = this.ele.height = window.innerHeight * 2
this.ele.style.width = this.ele.width * 0.5 + 'px'
this.ele.style.height = this.ele.height * 0.5 + 'px'
this.ctx = this.ele.getContext('2d')
this.ctx.scale(2, 2)
this.resizeCallback && this.resizeCallback()
},
run(callback) {
requestAnimationFrame(() => {
this.run(callback)
})
callback(this.ctx)
}
}
const ctx = canvas.init()
let objects = []
class SadMan {
drawHead(t) {
ctx.save()
ctx.beginPath()
ctx.translate(0, Math.sin(t) * 4)
ctx.arc(80, -35, 35, 0, 2 * Math.PI)
ctx.fill()
ctx.closePath()
ctx.restore()
}
drawBody(t) {
ctx.beginPath()
ctx.save()
ctx.rotate(Math.sin(t) * Math.PI / 180 * -1)
ctx.translate(0, Math.sin(t) * 4)
ctx.scale(0.5, 0.5)
const body = new Path2D('M125,284 L1,284 C0.33333333,94.6666667 35,0 105,0 C115.666667,4 122.333333,20.6666667 125,50 L125,284 Z')
ctx.fill(body)
ctx.restore()
ctx.closePath()
}
drawFeet(t) {
t = t / 2
ctx.save()
ctx.scale(0.5, 0.5)
ctx.translate(0, 460)
const foot = new Path2D('M23,0 C67,0 80,16 80,22 C80,26 78.6666667,28 76,28 C29.3333333,28 6,28 6,28 C6,28 -1.34111707e-14,30 0,17 C1.42108547e-14,4 10,1.9505735e-16 13,0 C16,0 13,0 23,0 Z')
ctx.save()
ctx.translate(Math.cos(t) * -50, Math.sin(t) > 0 ? Math.sin(t) * -35 : 0)
if(t < Math.PI) {
ctx.rotate(Math.sin(t) * Math.PI / 180 * -5)
}
ctx.fill(foot)
ctx.restore()
ctx.save()
ctx.translate(Math.cos(t + Math.PI) * -50, Math.sin(t + Math.PI) > 0 ? Math.sin(t + Math.PI) * -35 : 0)
if(t > Math.PI) {
ctx.rotate(Math.sin(t + Math.PI) * Math.PI / 180 * -5)
}
ctx.fill(foot)
ctx.restore()
ctx.restore()
}
drawShadow(t) {
ctx.beginPath()
ctx.save()
ctx.scale(0.5, 0.5)
ctx.translate(45, 490)
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'
ctx.ellipse(0, 0, 120 + Math.sin(t) * 10, 8, 0, 0, 2 * Math.PI)
ctx.fill()
ctx.restore()
ctx.closePath()
}
draw(t) {
t = t % Math.PI * 2
ctx.fillStyle = 'white'
ctx.save()
ctx.translate(window.innerWidth * 0.5 - 140, window.innerHeight * 0.5 - 80)
this.drawShadow(t)
this.drawHead(t)
this.drawBody(t)
this.drawFeet(t)
ctx.restore()
}
}
const init = () => {
objects = []
objects.push(new SadMan())
// objects.push(new Feet())
}
document.addEventListener('click', () => {
init()
})
init()
let tick = 0
canvas.run(ctx => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
tick += 0.03
objects.forEach(obj => {
obj.draw(tick)
})
})
canvas.onResize(() => {
init()
})
</script>
</html>