动画是画面随时间变动的效果。无论是用何种方式的随时间变动(setInterval, setTimeout), 都是可以驱”动”的; 但是在浏览器端, 使用 requestAnimationFrame 获取的定时间隔更平稳可靠。参考: MDN
以下是一个简单的驱动动画的方法, 我们把它命名为 Ticker.js, 用来驱动更新视图:
```javascript name=Ticker.js var _raf var _callbacks = [] var _tick = function () { _callbacks.forEach(cbArr => { var callback = cbArr[0] var scope = cbArr[1] callback.call(scope) })
window.requestAnimationFrame(_tick) }
class Ticker { constructor () { _raf = window.requestAnimationFrame(_tick) }
/**
* 注册一个每帧回调到 Ticker
* @param callback
* @param scope
*/
on (callback, scope) {
var result = _callbacks.filter(cbArr => {
return callback == cbArr[0] && scope == cbArr[1]
})
if(!result.length){
_callbacks.push([callback, scope])
}
}
/**
* 从 Ticker 内移除一个每帧回调
* @param callback
* @param scope
*/
off (callback, scope) {
for (var cbArr of _callbacks) {
if(cbArr[0] == callback || cbArr[1] == scope){
_callbacks.splice(_callbacks.indexOf(cbArr), 1)
}
}
} }
var instance = new Ticker() export default instance
结合上例, 让每个小图标(DisplayObject)单独在自己原本的位置周围做随机运动, 形成一种 'shaking' 的效果:
```javascript
Ticker.on(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
children.forEach(child => {
if (!child.originPos) {
child.originPos = {
x: child.x,
y: child.y
}
}
child.x = child.originPos.x + 10 * Math.random() - 5
child.y = child.originPos.y + 10 * Math.random() - 5
child.draw()
})
})
效果如下图:
获取交互坐标, 触碰检测, 层级管理
当点击画布时, 要想获得事件点相对于画布内的坐标位置, 可以通过如下方式:
canvas.addEventListener('click', (e) => {
// 获取canvas所占据的矩形区域
var rect = canvas.getBoundingClientRect()
var x = e.clientX - rect.left // px
var y = e.clientY - rect.top // px
// canvas内的实际坐标点
x *= canvas.width / canvas.offsetWidth
y *= canvas.height / canvas.offsetHeight
})
现在已经知道了画布内发生 click 事件的坐标(x, y), 为了判断在这次点击的位置触碰到了哪些显示对象, 需要一种碰撞检测的判定方法。 最简单的, 遍历全部子对象, 检查触碰点是否在这个子对象的矩形区域范围内。如果有触碰到的, 使它静止下来
// hit area detect
var hits = children.filter(child => {
return x >= child.x && x <= child.x + child.img.width && y >= child.y && y <= child.y + child.img.height
})
// 让触碰到的显示对象中心移动到(x, y)位置
hits.forEach(hit => {
hit.static = true // make it static
hit.x = x - hit.img.width / 2
hit.y = y - hit.img.height / 2
})
改进: 因为图片的内容实际上是圆形,以上的矩形区域检测并不精准。可以根据实际情况, 检测事件点到图标中心的距离是否小于图标半径来判定是否碰撞
var hits = children.filter(child => {
return Math.sqrt(Math.pow(child.x + child.img.width / 2 - x, 2) + Math.pow(child.y + child.img.height / 2 - y, 2)) <= child.img.width / 2
})
有时候, 点击到的显示对象可能会被其他的元素盖住, 此时需要改变对象的层级。好在可以通过 children 数组快速达成这一目标
hits.forEach((hit) => {
children.splice(children.indexOf(hit), 1)
children.push(hit)
})