UNPKG

@llds/bg-dots

Version:

A lightweight background dot animation using PixiJS and Simplex Noise.

149 lines (143 loc) 3.95 kB
import { Application, Graphics, Particle, ParticleContainer } from "pixi.js"; import { createNoise3D } from "simplex-noise"; //#region src/index.ts function clearCanvasIn(el) { const canvases = el.querySelectorAll("canvas"); canvases.forEach((canvas) => canvas.remove()); } /** * useBackgroundDots * @param container the container to add the dots to * @param isDark whether the background is dark or not */ function useBackgroundDots(container, isDark) { const SCALE = 200; const LENGTH = 5; const SPACING = 15; const noise3d = createNoise3D(); const existingPoints = /* @__PURE__ */ new Set(); const points = []; let w = window.innerWidth; let h = window.innerHeight; let app; let particleContainer; let dotTexture; clearCanvasIn(container); function getForceOnPoint(x, y, z) { return (noise3d(x / SCALE, y / SCALE, z) - .5) * 2 * Math.PI; } function createDotTexture(app$1) { const color = isDark ? 13421772 : 6710886; const g = new Graphics().circle(0, 0, .5).fill(color); return app$1.renderer.generateTexture(g); } function addPoints() { for (let x = -SPACING / 2; x < w + SPACING; x += SPACING) for (let y = -SPACING / 2; y < h + SPACING; y += SPACING) { const id = `${x}-${y}`; if (existingPoints.has(id)) continue; existingPoints.add(id); const particle = new Particle(dotTexture); particle.anchorX = .5; particle.anchorY = .5; particleContainer.addParticle(particle); const opacity = Math.random() * .5 + .5; points.push({ x, y, opacity, particle }); } } function resetParticles() { if (!app) return; app.stage.removeChild(particleContainer); particleContainer.destroy(); particleContainer = new ParticleContainer({ dynamicProperties: { position: true, alpha: true } }); app.stage.addChild(particleContainer); points.length = 0; existingPoints.clear(); addPoints(); } function throttle(fn, delay) { let timer = null; return () => { if (timer) clearTimeout(timer); timer = window.setTimeout(() => { fn(); timer = null; }, delay); }; } async function setup() { app = new Application(); await app.init({ backgroundAlpha: 0, antialias: true, resolution: window.devicePixelRatio, resizeTo: container, autoDensity: true, eventMode: "none" }); container.appendChild(app.canvas); const canvas = app.canvas; Object.assign(canvas.style, { position: "absolute", top: "0", left: "0", width: "100%", height: "100%", zIndex: "-1", pointerEvents: "none" }); particleContainer = new ParticleContainer({ dynamicProperties: { position: true, alpha: true } }); app.stage.addChild(particleContainer); dotTexture = createDotTexture(app); addPoints(); app.ticker.add(() => { const t = Date.now() / 1e4; for (const p of points) { const { x, y, opacity, particle } = p; const rad = getForceOnPoint(x, y, t); const len = (noise3d(x / SCALE, y / SCALE, t * 2) + .5) * LENGTH; const nx = x + Math.cos(rad) * len; const ny = y + Math.sin(rad) * len; particle.x = nx; particle.y = ny; particle.alpha = (Math.abs(Math.cos(rad)) * .8 + .2) * opacity; } }); const resizeHandler = throttle(() => { w = window.innerWidth; h = window.innerHeight; resetParticles(); }, 200); window.addEventListener("resize", resizeHandler); return () => { window.removeEventListener("resize", resizeHandler); app.destroy(true, { children: true, texture: true, textureSource: true }); }; } setup().then((dispose) => { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) for (const node of mutation.removedNodes) if (node === container) { dispose(); observer.disconnect(); return; } }); observer.observe(container.parentElement ?? document.body, { childList: true }); }).catch(console.error); } //#endregion export { useBackgroundDots };