@llds/bg-dots
Version:
A lightweight background dot animation using PixiJS and Simplex Noise.
149 lines (143 loc) • 3.95 kB
JavaScript
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 };