console
class Particle {
constructor({
x = 0, y = 0,
tx = 0, ty = 0,
vx = 0, vy = 0,
radius = 2, color = '#F00000'
}) {
this.x = x
this.y = y
this.tx = tx
this.ty = ty
this.vx = vx
this.vy = vy
this.xpos = 0
this.ypos = 0
this.zpos = 0
this.vz = 0
this.scaleX = 1
this.scaleY = 1
this.visible = true
this.radius = radius
this.color = color
}
draw(ctx) {
if (this.visible) {
ctx.save()
ctx.translate(this.x, this.y)
ctx.scale(this.scaleX, this.scaleY);
ctx.fillStyle = this.color
ctx.beginPath()
ctx.arc(0, 0, this.radius, 0, (Math.PI * 2), true)
ctx.closePath()
ctx.fill()
ctx.restore()
}
return this
}
}
function drawFrame(particles, finished) {
drawFrame.timer = window.requestAnimationFrame(() => {
drawFrame(particles, finished)
})
ctx.clearRect(0, 0, canvas.width, canvas.height)
const easing = 0.08
const finishedParticles = particles.filter(particle => {
const dx = particle.tx - particle.x
const dy = particle.ty - particle.y
let vx = dx * easing
let vy = dy * easing
if (Math.abs(dx) < 0.1 && Math.abs(dy) < 0.1) {
particle.finished = true
particle.x = particle.tx
particle.y = particle.ty
} else {
particle.x += vx
particle.y += vy
}
particle.draw(ctx)
return particle.finished
})
if (finishedParticles.length === particles.length) {
window.cancelAnimationFrame(drawFrame.timer)
finished && finished(particles)
}
};
function getPixels({ target, space = 5, callback }) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const viewWidth = window.innerWidth
const viewHeight = window.innerHeight
canvas.width = viewWidth
canvas.height = viewHeight
if (typeof target === 'string') {
ctx.font = '150px bold'
ctx.fillStyle = '#fff'
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
ctx.fillText(target, viewWidth / 2, (viewHeight) / 2)
} else {
ctx.drawImage(target, (viewWidth - target.width) / 2, (viewHeight - target.height) / 2, target.width, target.height)
}
const { data, width, height } = ctx.getImageData(0, 0, viewWidth, viewHeight)
const pixeles = []
for (let x = 0; x < width; x += space) {
for (let y = 0; y < height; y += space) {
const pos = (y * width + x) * 4
if (data[pos + 3] > 128) {
const pixele = { x, y, rgba: [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]] }
pixeles.push(callback ? callback(pixele) : pixele)
}
}
}
return pixeles
}
function createParticles({ target, radius, space }) {
return getPixels({
target,
space,
callback({ x, y, rgba }) {
return new Particle({
radius,
x: Math.random() * (50 + window.innerWidth) - 50,
y: Math.random() * (50 + window.innerHeight) - 50,
tx: x,
ty: y,
color: `rgba(${rgba})`
})
}
})
}
function broken(particles, init, finished) {
const vpX = canvas.width / 2
const vpY = canvas.height / 2
const fl = 250
const floor = canvas.height - 5
let gravity = 0
let bounce = 0
particles.forEach(particle => {
const {
vx = 0, vy = 0, vz = 0,
gravity: gravityValue = 0,
bounce: bounceValue = 0
} = init()
particle.vx = vx
particle.vy = vy
particle.vz = vz
gravity = gravityValue
bounce = bounceValue
})
function frame() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
broken.timer = requestAnimationFrame(frame)
particles = particles
.filter(particle => {
particle.vy += gravity
particle.xpos += particle.vx
particle.ypos += particle.vy
particle.zpos += particle.vz
if (particle.ypos > floor && bounce !== 0) {
particle.ypos = floor
particle.vy *= bounce
}
if (particle.zpos > -fl && particle.zpos < fl * 3 && Math.abs(particle.xpos) < canvas.width) {
const scale = fl / (fl + particle.zpos)
particle.scaleX = particle.scaleY = scale
particle.x = vpX + particle.xpos * scale
particle.y = vpY + particle.ypos * scale
particle.visible = true
} else {
particle.visible = false
}
return particle.visible
})
.sort((a, b) => b.zpos - a.zpos)
.map((particle, i) => {
particle.draw(ctx)
return particle
})
if (particles.length === 0) {
cancelAnimationFrame(broken.timer)
finished && finished()
console.log('end')
}
}
frame()
}
const effect = {
ejection() {
return {
vx: Math.random() * 10 - 5,
vy: Math.random() * -8 - 4,
vz: Math.random() * 4 - 2,
gravity: 0.2,
bounce: -0.4
}
},
dispersed() {
return {
vx: Math.random() * 10 - 5,
vy: Math.random() * 10 - 5,
vz: Math.random() * 10 - 8,
}
}
}
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
drawFrame(
createParticles({
target: "\ud83d\udc33",
radius: 3,
space: 8
}),
particles => {
broken(particles, effect.ejection)
}
)
document.body.addEventListener('click', event => {
const effectName = event.target.getAttribute('data-effect')
if (effectName && effect[effectName]) {
cancelAnimationFrame(drawFrame.timer)
cancelAnimationFrame(broken.timer)
drawFrame(
createParticles({
target: "\ud83d\udc33",
radius: 3,
space: 8
}),
particles => {
broken(particles, effect[effectName])
}
)
}
})
<canvas id="canvas"></canvas>
<div style="position:absolute;bottom: 0; left: 0;">
<button data-effect="ejection">弹射</button>
<button data-effect="dispersed">分散</button>
</div>
html, body {
width: 100%;
height: 100%;
background-color: #000;
overflow: hidden;
}