From 4f2dd94e83dd9c3d87cb90dad9775eb1a9ad7b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominique=20J=C3=BCrgensen?= <mail@dominiq.eu> Date: Sat, 25 Dec 2021 21:36:33 +0100 Subject: [PATCH] Add support for multiple layer of particles. --- src/parallax-css-testpage/particles.js | 280 ++++++++++-------------- src/parallax-css-testpage/template.html | 70 +++--- 2 files changed, 162 insertions(+), 188 deletions(-) diff --git a/src/parallax-css-testpage/particles.js b/src/parallax-css-testpage/particles.js index 9b59090b1..6667cb1b6 100644 --- a/src/parallax-css-testpage/particles.js +++ b/src/parallax-css-testpage/particles.js @@ -1,23 +1,24 @@ /* * Particles.js + * + * Particle emitter and renderer. */ const Debug = false const Static = true -// Needed for fps calculation. -let Then = Date.now() // Argh.. Global, mutable, variable..! - // // Helper // -// Random Value without zero function randomValue(min, max) { const value = (Math.random() * (+max - +min) + +min) - return (value === 0) ? randomValue(min, max) : value + // Prevent values that can be possible boring + return (value === 0 || value === 1) + ? randomValue(min, max) + : value } function getNodeYOffset(node) { @@ -30,38 +31,6 @@ function getNodeHeight(node) { return (dimensions) ? dimensions.height : window.innerHeight } -function getScene(canvasId, height) { - const width = window.innerWidth - const fov = 0.8 - const perspective = width * fov - const yOffset = 0 - const canvas = document.getElementById(canvasId) - const ctx = canvas.getContext('2d') - - ctx.canvas.width = width - ctx.canvas.height = window.innerHeight - - return { width, height, yOffset, perspective, ctx } -} - -// function to2dParticle(scene, particle) { -// const perspective = scene.perspective -// const scale = perspective / (perspective + particle.position.z) -// const yOffset = scene.yOffset * scale -// const offset = particle.size.value * 0.5 -// const x = particle.position.x - offset -// const y = (yOffset + (particle.position.y - offset)) -// const size = particle.size.value -// // const y = (yOffset + (particle.position.y - offset)) * scale -// // const size = particle.size.value * scale -// return { x, y, size } -// } - -function calcScrolledYPos(ctx, yPos, yOffset) { - const y = (yPos + yOffset) - return (y <= 0) ? ctx.canvas.height - y : y -} - // // Data @@ -72,10 +41,6 @@ function Point2D(x, y) { return { x, y } } -function Point3D(x, y, z) { - return { x, y, z } -} - function MinMax(min, max) { return { min, max } } @@ -90,16 +55,6 @@ function RandomPoint2D(minMaxX, minMaxY) { return Point2D(x, y) } -function RandomPoint3D(minMaxX, minMaxY, minMaxZ) { - const x = randomValue(minMaxX.min, minMaxX.max) - const y = randomValue(minMaxY.min, minMaxY.max) - const z = - (typeof minMaxZ === 'number') - ? minMaxZ - : randomValue(minMaxZ.min, minMaxZ.max) - return Point3D(x, y, z) -} - function Particle({ position, direction, size, color }) { return { position, direction, size, color } } @@ -132,8 +87,8 @@ function Update(scene, particle) { Object.assign({}, updateSizeStep, { size: Object.assign({}, updateSizeStep.size, { value: - // Todo: Animate puls effect with the help of Math.sin() updateSizeStep.size.value + updateSizeStep.size.step * updateSizeStep.size.direction, + // updateSizeStep.size.value }) }) @@ -142,8 +97,8 @@ function Update(scene, particle) { const sizeOffset = particle.size.value * 0.5 const y = particle.position.y - const top = y - sizeOffset - const bottom = y + sizeOffset + const top = y + sizeOffset + const bottom = y - sizeOffset const left = particle.position.x - sizeOffset const right = particle.position.x + sizeOffset @@ -156,10 +111,11 @@ function Update(scene, particle) { Object.assign({}, updateSize, { direction: Point2D( - // Bounce of the right and left edges of the screen. + // Bounce of the right and left edges of the scene (isRightEdge || isLeftEdge) ? updateSize.direction.x * -1 : updateSize.direction.x, + // Bounce from the top and bottom edges of the scene (isTopEdge || isBottomEdge) ? particle.direction.y * -1 : particle.direction.y, @@ -168,7 +124,7 @@ function Update(scene, particle) { const updatePosition = Object.assign({}, updateDirection, { position: - Point3D( + Point2D( updateDirection.position.x + updateDirection.direction.x, updateDirection.position.y + updateDirection.direction.y, // (isTopEdge) @@ -176,7 +132,6 @@ function Update(scene, particle) { // : (isBottomEdge) // ? -sizeOffset // : (updateDirection.position.y + updateDirection.direction.y), - updateDirection.position.z ) }) @@ -186,19 +141,20 @@ function Update(scene, particle) { function Draw(scene, particle) { // Cache object access - const size = particle.size.value + const size = particle.size.value + const position = particle.position - const perspective = scene.perspective - const scale = perspective / (perspective + particle.position.z) - const yOffset = scene.yOffset * scale + // Draw calculations + const yOffset = scene.yOffset * scene.depthScale const drawOffset = size * 0.5 - const y = yOffset + (particle.position.y - drawOffset) + const y = yOffset + (position.y - drawOffset) const ctx = scene.ctx - const isVisible = (size > 0) && (y < window.innerHeight) && (y > 0) + + // Draw if (isVisible) { ctx.fillStyle = particle.color - ctx.fillRect(particle.position.x - drawOffset, y, size, size) + ctx.fillRect(position.x - drawOffset, y, size, size) } } @@ -209,122 +165,126 @@ function Clear(scene) { function Render(scene, particles) { Clear(scene) particles.forEach( - function (particle) { + function doDraw(particle) { Draw(scene, particle) } ) } -function Animate({ scene, fpsInterval, particles, anchorNode }) { - scene.yOffset = getNodeYOffset(anchorNode) - - // Update particle positions and register for the next frame - // to render. - requestAnimationFrame(() => { - const updatedParticles = - particles.map(function (p) { - return Update(scene, p) - }) - Animate({ scene, fpsInterval, particles: updatedParticles, anchorNode }) - }) - - // Render particles to canvas and do some fps calculations - const now = Date.now() - const elapsed = now - Then - if (elapsed > fpsInterval) { - Then = now - (elapsed % fpsInterval) +function Animate(scene, particles) { + var fpsInterval = scene.fpsInterval + var now = Date.now() + var then = scene.then + var elapsed = now - then + + // Just render when we're reaching the configured fps + if (elapsed > scene.fpsInterval) { + scene.yOffset = getNodeYOffset(scene.anchorNode) + scene.then = now - (elapsed % fpsInterval) Render(scene, particles) } + + // Update particle positions for the next render call + const updatedParticles = particles.map(function doUpdate(p) { + return Update(scene, p) + }) + + // Function to animate the next frame + return function () { + return Animate(scene, updatedParticles) + } } +function CreateScene({ canvasId, contentId, depth, fps }) { + const contentNode = document.getElementById(contentId) + const height = getNodeHeight(contentNode) -// -// Api -// + const fpsInterval = 1000 / fps + const width = window.innerWidth + const yOffset = 0 + const canvas = document.getElementById(canvasId) + const ctx = canvas.getContext('2d') + const depthScaledHeight = height * (depth+1) + ctx.canvas.width = width + ctx.canvas.height = window.innerHeight + + return { + width, + height: depthScaledHeight, + yOffset, + depthScale: depth, + ctx, + fps, + fpsInterval, + then: Date.now(), + anchorNode: contentNode + } +} -function Particles({ - canvasId, - contentId, - fps, + +function CreateParticles(scene, { amount, color, size, speed, - depth, lifespan, }) { - const contentNode = document.getElementById(contentId) - const height = getNodeHeight(contentNode) + return Array.from( + { length: amount }, + function createParticle() { + return Particle({ + position: + RandomPoint2D( + MinMax(0, (scene.width - (size * 2))), + MinMax(0, (scene.height - (size * 2))), + ), + direction: + RandomPoint2D( + MinMax(-speed, speed), + MinMax(-speed, speed) + ), + size: { + value: randomValue(-size, size), + bound: MinMax(-size, size), + step: size / ((lifespan) / scene.fps), + direction: + // 50% Chance for the particle floating in one + // direction or the other. + (randomValue(0, 100) < 50) + ? -1 + : +1, + }, + color + }) + } + ) +} - // Init the canvas for rendering. - // - const scene = getScene(canvasId, height) - if (!scene.ctx) { - console.error("Particles: Can't find Canvas ", canvasId) - return - } - - // Generate some particles - // +// +// Api +// - const z = depth * -1000 - const particles = - (Debug) - ? (Static) - ? [ Particle({ - position: Point3D(100, 500, z), - direction: Point2D(0, 0), - size: { - value: 15, - bound: MinMax(100, 100), - step: 0, - direction: 1 - }, - color: 'white' - }) ] - : [ Particle({ - position: Point3D(100, 100, z), - direction: Point2D(0, +1), - size: { - value: 100, - bound: MinMax(100, 100), - step: 0, - direction: 1 - }, - color: 'white' - }) ] - : Array.from( - { length: amount }, - () => Particle({ - position: - RandomPoint3D( - MinMax(0, (scene.width - (size*2))), - MinMax(0, (scene.height - (size*2))), - z - ), - direction: - RandomPoint2D( - MinMax(-speed, speed), - MinMax(-speed, speed) - ), - size: { - value: randomValue(-size, size), - bound: MinMax(-size, size), - step: size / ((lifespan) / fps), - direction: - (randomValue(0, 100) < 50) - ? -1 - : +1, - }, - color + +function Particles(configs) { + function doRenderParticles(rendererList) { + requestAnimationFrame(function () { + doRenderParticles( + rendererList.map(function doRender(render) { + return render() }) ) - - // Prepare Animation and animate - const fpsInterval = 1000 / fps - Then = Date.now() - Animate({ scene, fpsInterval, particles, anchorNode: contentNode }) - return -} + }) + } + + return function () { + doRenderParticles( + configs.map(function (particleConfig) { + var scene = CreateScene(particleConfig.scene) + var particles = CreateParticles(scene, particleConfig.particles) + return Animate(scene, particles) + }) + ) + } +} \ No newline at end of file diff --git a/src/parallax-css-testpage/template.html b/src/parallax-css-testpage/template.html index 7819f5ee9..5fcfa853e 100644 --- a/src/parallax-css-testpage/template.html +++ b/src/parallax-css-testpage/template.html @@ -82,36 +82,50 @@ <script> - var bgParticles = { - // Id of the canvas to render to - canvasId: 'bg-particles', - - // Id of the content container, we're using it to get the - // rendering height and it's the anchor to get the y offset - // to simulate scrolling. - contentId: 'content', - fps: 16, - amount: 120, - color: 'white', - size: 10, - speed: 0.2, - depth: -8, - lifespan: 4000 // in milliseconds + var fgParticleConfig = { + scene: { + // Id of the canvas to render to + canvasId: 'fg-particles', + // Id of the content container, we're using it to get the + // rendering height and it's the anchor to get the y offset + // to simulate scrolling. + contentId: 'content', + fps: 30, + depth: 2, + }, + particles: { + amount: 100, + color: 'white', + size: 30, + speed: 0.5, + lifespan: 3000 // in milliseconds + } } - Particles(bgParticles) - - var fgParticles = { - canvasId: 'fg-particles', - contentId: 'content', - fps: 24, - amount: 20, - color: 'white', - size: 30, - speed: 0.5, - depth: 2, - lifespan: 4000 // in milliseconds + var bgParticleConfig = { + scene: { + // Id of the canvas to render to + canvasId: 'bg-particles', + // Id of the content container, we're using it to get the + // rendering height and it's the anchor to get the y offset + // to simulate scrolling. + contentId: 'content', + fps: 16, + depth: 0.08, + }, + particles: { + amount: 200, + color: 'white', + size: 10, + speed: 0.2, + lifespan: 4000 // in milliseconds + } } - // Particles(fgParticles) + + var animate = Particles([ + fgParticleConfig, + bgParticleConfig, + ]) + animate() </script> </body> -- GitLab