From 680235525a0c038a29df3866c4a94d017b2a1fce Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 10:43:05 -0700 Subject: [PATCH 1/7] refactor: move particle initialization to a reset method --- source/lib/particle.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/source/lib/particle.js b/source/lib/particle.js index 86b67b5..677fe68 100644 --- a/source/lib/particle.js +++ b/source/lib/particle.js @@ -13,8 +13,7 @@ function getWaveFn(fn, p, min, max, o = 0) { export default class Particle { constructor(x = 0, y = 0, frames) { - this.px = x; - this.py = y; + this.reset(x, y); this.frames = frames; this.frame = -1; @@ -25,20 +24,10 @@ export default class Particle { return floor(waveFn(ts)); } - this.rotation = 0; this.spin = (π - random() * ππ) / 500; - - this.vx = 0.75 - random() * 1.5; - this.vy = 0.75 - random() * 1.5; - this.drag = 0.98; this.grav = 0.025; - - this.radius = 16; this.scale = 1.025; - - this.hue = 242; - this.alpha = 1; this.fade = 0.01; } @@ -102,4 +91,17 @@ export default class Particle { ctx.drawImage(img, x, y, width, height); ctx.restore(); } + + reset(x, y) { + this.px = x; + this.py = y; + + this.vx = 0.75 - random() * 1.5; + this.vy = 0.75 - random() * 1.5; + + this.radius = 16; + this.rotation = 0; + this.alpha = 1; + this.hue = 242; + } } From fbd1a80e1ea3f418853b38135ad8987e5070bb9d Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 11:31:39 -0700 Subject: [PATCH 2/7] refactor: don't recreate the wave function every time you call the frame function dummy --- source/lib/particle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/lib/particle.js b/source/lib/particle.js index 677fe68..7248a15 100644 --- a/source/lib/particle.js +++ b/source/lib/particle.js @@ -19,8 +19,8 @@ export default class Particle { this.frame = -1; const p = 400 + floor(random() * 400), o = floor(random() * p); + const waveFn = getWaveFn(saw, p, 0, 4, o); this.frameFn = function(ts) { - const waveFn = getWaveFn(saw, p, 0, 4, o); return floor(waveFn(ts)); } From 451d247474fb7e82b2b372edab958f0d76e9861b Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 11:32:20 -0700 Subject: [PATCH 3/7] refactor: only increment the frame if there's a frame to increment --- source/lib/particle.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/lib/particle.js b/source/lib/particle.js index 7248a15..c4efc8e 100644 --- a/source/lib/particle.js +++ b/source/lib/particle.js @@ -45,7 +45,9 @@ export default class Particle { this.radius *= this.scale; this.alpha -= this.fade; - this.frame = this.frameFn(ts); + if (this.frames) { + this.frame = this.frameFn(ts); + } } render(ctx) { From 2a1f0e04daab5613210ab5b49f5249b3c42bb053 Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 12:28:53 -0700 Subject: [PATCH 4/7] refactor: apply the background color to the body --- source/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/source/index.html b/source/index.html index 0b7c6f4..244d5bf 100644 --- a/source/index.html +++ b/source/index.html @@ -25,11 +25,8 @@ } body { - margin: 0; - } - - canvas { background-color: #000; + margin: 0; } From d681a6dcaefb71fa301d54ee4963b7700a43c2d2 Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 12:32:06 -0700 Subject: [PATCH 5/7] refactor: pre-render the ball into an in-memory image element --- source/lib/ball.js | 32 ++++++++++++++++++++++++++++++++ source/lib/particle.js | 26 +++++++------------------- 2 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 source/lib/ball.js diff --git a/source/lib/ball.js b/source/lib/ball.js new file mode 100644 index 0000000..91e99a9 --- /dev/null +++ b/source/lib/ball.js @@ -0,0 +1,32 @@ +const { PI: π } = Math; +const ππ = 2 * π; + +const cvs = document.createElement('canvas'); +const ctx = cvs.getContext('2d'); +const r = 256, hue = 242, x = r / 2, y = r / 2; + +cvs.width = cvs.height = r; +ctx.clearRect(0, 0, r, r); + +ctx.fillStyle = `hsla(${hue},100%,50%,0.4)`; +ctx.beginPath(); +ctx.arc(x, y, r / 2, 0, ππ); +ctx.closePath(); +ctx.fill(); + +ctx.fillStyle = `hsla(${hue},100%,75%,0.75)`; +ctx.beginPath(); +ctx.arc(x, y, r / 4, 0, ππ); +ctx.closePath(); +ctx.fill(); + +ctx.fillStyle = `hsla(${hue},0%,100%,1)`; +ctx.beginPath(); +ctx.arc(x, y, r / 8, 0, ππ); +ctx.closePath(); +ctx.fill(); + +const ball = new Image(); +ball.src = cvs.toDataURL('image/png'); + +export default ball; diff --git a/source/lib/particle.js b/source/lib/particle.js index c4efc8e..9950c84 100644 --- a/source/lib/particle.js +++ b/source/lib/particle.js @@ -1,3 +1,5 @@ +import ball from './ball'; + const { PI: π, floor, max, random } = Math; const ππ = 2 * π; @@ -60,25 +62,11 @@ export default class Particle { } renderBall(ctx) { - const { px, py, radius, hue } = this; - - ctx.fillStyle = `hsla(${hue},100%,50%,0.4)`; - ctx.beginPath(); - ctx.arc(px, py, radius, 0, ππ); - ctx.closePath(); - ctx.fill(); - - ctx.fillStyle = `hsla(${hue},100%,75%,0.75)`; - ctx.beginPath(); - ctx.arc(px, py, radius / 2, 0, ππ); - ctx.closePath(); - ctx.fill(); - - ctx.fillStyle = `hsla(${hue},0%,100%,1)`; - ctx.beginPath(); - ctx.arc(px, py, radius / 4, 0, ππ); - ctx.closePath(); - ctx.fill(); + const { px, py, radius } = this; + const x = px - radius; + const y = py - radius; + + ctx.drawImage(ball, x, y, radius * 2, radius * 2); } renderFrame(ctx) { From 7651a377e55b182930aac3285393c1effe8759f1 Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 12:37:43 -0700 Subject: [PATCH 6/7] refactor: pool particles, only create 1,000 and reset them on reuse --- source/main.js | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/source/main.js b/source/main.js index 29c8bd9..498d766 100644 --- a/source/main.js +++ b/source/main.js @@ -3,7 +3,11 @@ import load from './app/load'; import Particle from './lib/particle'; const { random, round } = Math; -const particles = []; +const particles = { + active: [], + pooled: [], + size: 1000, +}; load([ './images/sparkle-1-0.png', @@ -11,9 +15,14 @@ load([ './images/sparkle-1-2.png', './images/sparkle-1-1.png', ], images => { + let { active, pooled, size } = particles; + while (pooled.length < size) { + pooled.push(new Particle(-1, -1, (round(random()) ? images : null))); + } + init((ctx, { ts, dts }, { w, h, hw, hh, x, y, down }) => { - // update and render each particle - particles.forEach(particle => { + active = active.reduce((accumulator, particle) => { + // update each particle particle.update(ts, dts); if (particle.py > h - 50) { @@ -21,21 +30,34 @@ load([ particle.vy = -particle.vy * 0.8; } - particle.render(ctx); - }); + // pool "dead" particles + const isTransparent = particle.alpha <= 0; + const isOutside = particle.px < 0 || w < particle.px; + if (isTransparent || isOutside) { + pooled.push(particle); + return accumulator; + } - // if we go above a limit, start removing particles - while (particles.length > 1000) { - particles.shift(); - } + // render each particle + particle.render(ctx); + accumulator.push(particle); + return accumulator; + }, []); - // create a new particle (or 10) per frame + // "depool" a particle (or 10) per frame for (let i = down ? 10 : 1; i; --i) { - particles.push(new Particle(x, y, (round(random()) ? images : null))); + if (pooled.length === 0) { + return; + } + + const particle = pooled.shift(); + particle.reset(x, y); + active.push(particle); } // a simple particle counter ctx.fillStyle = '#0ff'; - ctx.fillText(particles.length.toLocaleString(), 8, 56); + ctx.fillText('pooled: ' + pooled.length.toLocaleString(), 8, 56); + ctx.fillText('active: ' + active.length.toLocaleString(), 8, 84); }); }); From 9079ec08f8d1bad40fbaffb4c2bd44b3c5f0c303 Mon Sep 17 00:00:00 2001 From: Matt Hayes Date: Thu, 28 Jul 2016 12:41:08 -0700 Subject: [PATCH 7/7] docs: improve comment --- source/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/main.js b/source/main.js index 498d766..0bf8671 100644 --- a/source/main.js +++ b/source/main.js @@ -30,7 +30,7 @@ load([ particle.vy = -particle.vy * 0.8; } - // pool "dead" particles + // pool "dead" particles, and bail early const isTransparent = particle.alpha <= 0; const isOutside = particle.px < 0 || w < particle.px; if (isTransparent || isOutside) {