最近在瞎逛的时候,无意间去到某个大佬的博客,看到其点击鼠标有一个酷炫的小球散开的效果,按了F12,发现是canvas实现的,于是我决定自己动手来实现一下。
从简到难
-
- 点击生球(在画布里鼠标点击的地方画个球)
-
- 让小球抛物线下坠
-
- 生成多个小球执行抛物线下坠
点击生球
通过点击事件获取到鼠标的坐标然后用canvas绘制小球球
// html
<canvas id="canvas" width="600" height="600"></canvas>
// js
const canvas = document.getElementById('canvas');
const ctx =canvas.getContext('2d');
canvas.addEventListener('click', function(e){
ctx.fillStyle = '#000'
ctx.beginPath()
ctx.arc(e.clientX, e.clientY, 5, 0, Math.PI * 2) // 绘制圆
ctx.fill()
})
点击画布即可生球,具体效果如下
让小球抛物线下坠
思路: 获取小球的某一时刻抛物线的坐标,动态的绘制小球;每次绘制当前的小球之前清除画布,不展示小球的轨迹
抛物线的轨迹公式如下
// angle为抛物线发射角度
// speed为发射速率
// renderCount 可以理解为渲染的某一时刻
const x = (Math.sin(angle) * speed) + x
const y = (Math.cos(angle) * speed) + y + (renderCount * 0.3)
得到公式之后,就可以遍历获取小球抛物线不同时刻的位置并绘制
const canvas = document.getElementById('canvas');
const ctx =canvas.getContext('2d');
const angle = Math.PI / 4 // 角度
const speed = 3 // 速率
let renderCount = 0
// 小球起始位置
const origin = {
x: 0,
y: 0
}
canvas.addEventListener('click', function(e){
origin.x = e.clientX
origin.y = e.clientY
run()
})
// 绘制小球
function drawCircle (x, y) {
ctx.fillStyle = '#000'
ctx.beginPath()
ctx.arc(x, y, 5, 0, Math.PI * 2)
ctx.fill()
}
// 绘制抛物线轨迹
function run () {
origin.x = (Math.sin(angle) * speed) + origin.x
origin.y = (Math.cos(angle) * speed) + origin.y + (renderCount * 0.3)
// 如果超出画布则停止绘制
if (origin.x > 600 || origin.y> 600) {
renderCount = 0
return
}
ctx.clearRect(0, 0, 600, 600) // 清除画布
drawCircle(origin.x, origin.y)
requestAnimationFrame(run.bind(this))
renderCount++
}
生成多个小球执行抛物线下坠
要让多个小球从不同方向与距离执行抛物线运动,只需要生成多个小球并以不一样的角度和初速度执行运动即可
首先我们需要定义一个球类,需要有如下参数
- 点击的原始坐标
- 初速度
- 角度
- 球体颜色
- 绘制画布的上下文
class Ball {
constructor({ origin, speed, angle, color, context }) {
this.origin = origin // 起始坐标
this.position = { ...this.origin } // 运动轨迹坐标
this.color = color // 填充颜色
this.speed = speed // 速率
this.angle = angle // 角度
this.context = context
this.renderCount = 0
}
draw() {
// 抛物线运动轨迹坐标计算
this.position.x = (Math.sin(this.angle) * this.speed) + this.position.x
this.position.y = (Math.cos(this.angle) * this.speed) + this.position.y + (this.renderCount * 0.3)
// 绘制小球
this.context.fillStyle = this.color
this.context.beginPath()
this.context.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2)
this.context.fill()
this.renderCount++
}
}
然后我们还需要定义一个类,作为每次点击生成一次动画的构造对象,可以配置每次点击生成多少个小球执行运动
class Boom {
constructor({ origin, count = 10, context, area }) {
this.origin = origin // 起始坐标
this.count = count // 小球数量
this.context = context
this.area = area // 画布的大小
this.stop = false // 运动状态
this.balls = []
}
// 初始化创建指定数量随机小球
init () {
for(let i = 0; i < this.count; i++) {
const ball = new Ball({
origin: { x: this.origin.x, y: this.origin.y },
context: this.context,
color: randomColor(),
angle: randomRange(Math.PI - 1, Math.PI + 1),
speed: randomRange(1, 6)
})
this.balls.push(ball)
}
}
draw () {
this.balls.forEach((ball, index) => {
// 如果超出画布
if (ball.position.x > this.area.width || ball.position.y > this.area.height) {
return this.balls.splice(index, 1)
}
ball.draw()
})
if (this.balls.length == 0) {
this.stop = true
}
}
}
最后一步,创建一个跟屏幕大小一样的画布,监听mousedown事件,每次点击就生成一个Boom的实例并执行动画即可
const canvas = document.createElement('canvas');
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const ctx =canvas.getContext('2d');
document.body.append(canvas)
const booms = []
let running = false
function handleMouseDown(e) {
const boom = new Boom({
origin: {x: e.clientX, y: e.clientY},
count: 20,
context: ctx,
area: {width: window.innerWidth, height: window.innerHeight}
})
boom.init()
booms.push(boom)
// 执行运动
const run = () => {
running = true
if (booms.length == 0) {
return running = false
}
requestAnimationFrame(run.bind(this))
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight)
booms.forEach((item,index) => {
if (item.stop) {
return booms.splice(index, 1)
}
item.draw()
})
}
running || run()
}
window.addEventListener('mousedown', handleMouseDown.bind(this))
最后看一下效果