UI
파티클 기초
2023-05-10 10:01
결과물

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.9/dat.gui.min.js" integrity="sha512-WoO4Ih0CDOSLYafy22wZD/mcJ7k0ESLqtQsFa6zFKnEUrbtuGU+GkLtVhgt93xa2qewG5gKEC6CWlN8OaCTSVg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
filter: url('#gooey');
}
</style>
</head>
<body>
<canvas></canvas>
<svg>
<defs>
<filter id="gooey">
<feGaussianBlur in="SourceGraphic" stdDeviation="40" result="blur1" />
<feColorMatrix in="blur1" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -23"/>
</filter>
</defs>
</svg>
<script type="module" src="./index.js"></script>
</body>
</html>
htmljavascript
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const dpr = window.devicePixelRatio
let canvasWidth
let canvasHeight
const randomNumBetween = (min, max) => {
return Math.random() * (max - min + 1) + min
}
let particles
let interval = 1000 / 60
let now, delta
let then = Date.now()
const feGaussianBlur = document.querySelector('feGaussianBlur')
const feColorMatrix = document.querySelector('feColorMatrix')
const controls = new function () {
this.blurValue = 40
this.alphaChannel = 100
this.alphaOffset = -23
this.acc = 1.03
}
let gui = new dat.GUI()
const f1 = gui.addFolder('Gooey Effect')
f1.open()
f1.add(controls, 'blurValue', 0, 100).onChange(v => {
feGaussianBlur.setAttribute('stdDeviation', v)
})
f1.add(controls, 'alphaChannel', 1, 200).onChange(v => {
feColorMatrix.setAttribute('values', `1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ${v} ${controls.alphaOffset}`)
})
f1.add(controls, 'alphaOffset', -40, 40).onChange(v => {
feColorMatrix.setAttribute('values', `1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ${controls.alphaChannel} ${v}`)
})
const f2 = gui.addFolder('Particle Property')
f2.open()
f2.add(controls, 'acc', 1, 1.5, 0.01).onChange(v => {
particles.forEach(particle => particle.acc = v)
})
class Particle {
constructor(x, y, radius, vy) {
this.x = x
this.y = y
this.radius = radius
this.vy = vy
this.acc = 1.03
this.count = 100
}
update() {
this.vy *= this.acc
this.y += this.vy
}
draw() {
ctx.beginPath()
ctx.arc(this.x, this.y, this.radius, 0, Math.PI / 180 * 360)
ctx.fillStyle = 'orange'
ctx.fill()
ctx.closePath()
}
}
function init() {
canvasWidth = innerWidth
canvasHeight = innerHeight
canvas.width = canvasWidth * dpr
canvas.height = canvasHeight * dpr
ctx.scale(dpr, dpr)
canvas.style.width = canvasWidth + 'px'
canvas.style.height = canvasHeight + 'px'
particles = []
const TOTAL = canvasWidth / 100
for (let i = 0; i < TOTAL; i++) {
const x = randomNumBetween(0, canvasWidth)
const y = randomNumBetween(0, canvasHeight)
const radius = randomNumBetween(50, 100)
const vy = randomNumBetween(1, 5)
const particle = new Particle(x, y, radius, vy)
particles.push(particle)
}
}
function animate() {
window.requestAnimationFrame(animate)
now = Date.now()
delta = now - then
if (delta < interval) return
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
particles.forEach(particle => {
particle.update()
particle.draw()
if (particle.y - particle.radius > canvasHeight) {
particle.y = -particle.radius
particle.x = randomNumBetween(0, canvasWidth)
particle.radius = randomNumBetween(50, 100)
particle.vy = randomNumBetween(1, 5)
}
})
then = now - (delta % interval)
}
window.addEventListener('load', () => {
init()
animate()
})
window.addEventListener('resize', () => {
init()
})
javascript