@arolariu/components
Version:
🎨 70+ beautiful, accessible React components built on Base UI. TypeScript-first, CSS Modules styling, tree-shakeable, SSR-ready. Perfect for modern web apps, design systems & rapid prototyping. Zero config, maximum flexibility! ⚡
365 lines (364 loc) • 13 kB
JavaScript
"use client";
import { jsx, jsxs } from "react/jsx-runtime";
import { motion } from "motion/react";
import { cn } from "../../lib/utilities.js";
import hole_background_module from "./hole-background.module.js";
import * as __rspack_external_react from "react";
const linear = (progress)=>progress;
const easeInExpo = (progress)=>0 === progress ? 0 : 2 ** (10 * (progress - 1));
const createEmptyDisc = ()=>({
p: 0,
x: 0,
y: 0,
w: 0,
h: 0
});
const createEmptyParticleArea = ()=>({
sx: 0,
sw: 0,
ex: 0,
ew: 0,
h: 0
});
const createInitialState = ()=>({
discs: [],
lines: [],
particles: [],
clip: {
disc: createEmptyDisc(),
i: 0,
path: null
},
startDisc: createEmptyDisc(),
endDisc: createEmptyDisc(),
rect: {
width: 0,
height: 0
},
render: {
width: 0,
height: 0,
dpi: 1
},
particleArea: createEmptyParticleArea(),
linesCanvas: null
});
const HoleBackground = /*#__PURE__*/ __rspack_external_react.forwardRef(({ strokeColor = "#737373", numberOfLines = 50, numberOfDiscs = 50, particleRGBColor = [
255,
255,
255
], className, children, ...props }, ref)=>{
const canvasRef = __rspack_external_react.useRef(null);
const animationFrameIdRef = __rspack_external_react.useRef(0);
const stateRef = __rspack_external_react.useRef(createInitialState());
__rspack_external_react.useImperativeHandle(ref, ()=>canvasRef.current, []);
const tweenValue = __rspack_external_react.useCallback((start, end, progress, ease = null)=>{
const delta = end - start;
const easeFunction = "inExpo" === ease ? easeInExpo : linear;
return start + delta * easeFunction(progress);
}, []);
const tweenDisc = __rspack_external_react.useCallback((disc)=>{
const { startDisc, endDisc } = stateRef.current;
disc.x = tweenValue(startDisc.x, endDisc.x, disc.p);
disc.y = tweenValue(startDisc.y, endDisc.y, disc.p, "inExpo");
disc.w = tweenValue(startDisc.w, endDisc.w, disc.p);
disc.h = tweenValue(startDisc.h, endDisc.h, disc.p);
}, [
tweenValue
]);
const setSize = __rspack_external_react.useCallback(()=>{
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
stateRef.current.rect = {
width: rect.width,
height: rect.height
};
stateRef.current.render = {
width: rect.width,
height: rect.height,
dpi: globalThis.window.devicePixelRatio || 1
};
canvas.width = stateRef.current.render.width * stateRef.current.render.dpi;
canvas.height = stateRef.current.render.height * stateRef.current.render.dpi;
}, []);
const setDiscs = __rspack_external_react.useCallback(()=>{
const { width, height } = stateRef.current.rect;
stateRef.current.discs = [];
stateRef.current.startDisc = {
x: 0.5 * width,
y: 0.45 * height,
w: 0.75 * width,
h: 0.7 * height,
p: 0
};
stateRef.current.endDisc = {
x: 0.5 * width,
y: 0.95 * height,
w: 0,
h: 0,
p: 0
};
let previousBottom = height;
stateRef.current.clip = {
disc: createEmptyDisc(),
i: 0,
path: null
};
for(let index = 0; index < numberOfDiscs; index += 1){
const progress = index / numberOfDiscs;
const disc = {
p: progress,
x: 0,
y: 0,
w: 0,
h: 0
};
tweenDisc(disc);
const bottom = disc.y + disc.h;
if (bottom <= previousBottom) stateRef.current.clip = {
disc: {
...disc
},
i: index,
path: null
};
previousBottom = bottom;
stateRef.current.discs.push(disc);
}
const clipPath = new globalThis.Path2D();
const { disc } = stateRef.current.clip;
clipPath.ellipse(disc.x, disc.y, disc.w, disc.h, 0, 0, 2 * Math.PI);
clipPath.rect(disc.x - disc.w, 0, 2 * disc.w, disc.y);
stateRef.current.clip.path = clipPath;
}, [
numberOfDiscs,
tweenDisc
]);
const setLines = __rspack_external_react.useCallback(()=>{
const { width, height } = stateRef.current.rect;
stateRef.current.lines = [];
const linesAngle = 2 * Math.PI / numberOfLines;
for(let index = 0; index < numberOfLines; index += 1)stateRef.current.lines.push([]);
stateRef.current.discs.forEach((disc)=>{
for(let index = 0; index < numberOfLines; index += 1){
const angle = index * linesAngle;
const point = {
x: disc.x + Math.cos(angle) * disc.w,
y: disc.y + Math.sin(angle) * disc.h
};
stateRef.current.lines[index].push(point);
}
});
const offCanvas = globalThis.document.createElement("canvas");
offCanvas.width = width;
offCanvas.height = height;
const context = offCanvas.getContext("2d");
const clipPath = stateRef.current.clip.path;
if (!context || !clipPath) return;
stateRef.current.lines.forEach((line)=>{
context.save();
let lineIsIn = false;
line.forEach((point, lineIndex)=>{
if (0 === lineIndex) return;
const previousPoint = line[lineIndex - 1];
if (!lineIsIn && (context.isPointInPath(clipPath, point.x, point.y) || context.isPointInStroke(clipPath, point.x, point.y))) lineIsIn = true;
else if (lineIsIn) context.clip(clipPath);
context.beginPath();
context.moveTo(previousPoint.x, previousPoint.y);
context.lineTo(point.x, point.y);
context.strokeStyle = strokeColor;
context.lineWidth = 2;
context.stroke();
context.closePath();
});
context.restore();
});
stateRef.current.linesCanvas = offCanvas;
}, [
numberOfLines,
strokeColor
]);
const initParticle = __rspack_external_react.useCallback((start = false)=>{
const { particleArea } = stateRef.current;
const sx = particleArea.sx + particleArea.sw * Math.random();
const ex = particleArea.ex + particleArea.ew * Math.random();
const dx = ex - sx;
const y = start ? particleArea.h * Math.random() : particleArea.h;
const radius = 0.5 + 4 * Math.random();
const vy = 0.5 + Math.random();
return {
x: sx,
sx,
dx,
y,
vy,
p: 0,
r: radius,
c: `rgba(${particleRGBColor[0]}, ${particleRGBColor[1]}, ${particleRGBColor[2]}, ${Math.random()})`
};
}, [
particleRGBColor
]);
const setParticles = __rspack_external_react.useCallback(()=>{
const { width, height } = stateRef.current.rect;
stateRef.current.particles = [];
const { disc } = stateRef.current.clip;
stateRef.current.particleArea = {
sx: (width - 0.5 * disc.w) / 2,
sw: 0.5 * disc.w,
ex: (width - 2 * disc.w) / 2,
ew: 2 * disc.w,
h: 0.85 * height
};
for(let index = 0; index < 100; index += 1)stateRef.current.particles.push(initParticle(true));
}, [
initParticle
]);
const drawDiscs = __rspack_external_react.useCallback((context)=>{
context.strokeStyle = strokeColor;
context.lineWidth = 2;
const outerDisc = stateRef.current.startDisc;
context.beginPath();
context.ellipse(outerDisc.x, outerDisc.y, outerDisc.w, outerDisc.h, 0, 0, 2 * Math.PI);
context.stroke();
context.closePath();
const clipPath = stateRef.current.clip.path;
stateRef.current.discs.forEach((disc, index)=>{
if (index % 5 !== 0) return;
if (clipPath && disc.w < stateRef.current.clip.disc.w - 5) {
context.save();
context.clip(clipPath);
}
context.beginPath();
context.ellipse(disc.x, disc.y, disc.w, disc.h, 0, 0, 2 * Math.PI);
context.stroke();
context.closePath();
if (clipPath && disc.w < stateRef.current.clip.disc.w - 5) context.restore();
});
}, [
strokeColor
]);
const drawLines = __rspack_external_react.useCallback((context)=>{
if (stateRef.current.linesCanvas) context.drawImage(stateRef.current.linesCanvas, 0, 0);
}, []);
const drawParticles = __rspack_external_react.useCallback((context)=>{
const clipPath = stateRef.current.clip.path;
if (!clipPath) return;
context.save();
context.clip(clipPath);
stateRef.current.particles.forEach((particle)=>{
context.fillStyle = particle.c;
context.beginPath();
context.rect(particle.x, particle.y, particle.r, particle.r);
context.closePath();
context.fill();
});
context.restore();
}, []);
const moveDiscs = __rspack_external_react.useCallback(()=>{
stateRef.current.discs.forEach((disc)=>{
disc.p = (disc.p + 0.001) % 1;
tweenDisc(disc);
});
}, [
tweenDisc
]);
const moveParticles = __rspack_external_react.useCallback(()=>{
stateRef.current.particles.forEach((particle, index)=>{
particle.p = 1 - particle.y / Math.max(stateRef.current.particleArea.h, 1);
particle.x = particle.sx + particle.dx * particle.p;
particle.y -= particle.vy;
if (particle.y < 0) stateRef.current.particles[index] = initParticle();
});
}, [
initParticle
]);
const tick = __rspack_external_react.useCallback(()=>{
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext("2d");
if (!context) return;
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
context.scale(stateRef.current.render.dpi, stateRef.current.render.dpi);
moveDiscs();
moveParticles();
drawDiscs(context);
drawLines(context);
drawParticles(context);
context.restore();
animationFrameIdRef.current = globalThis.requestAnimationFrame(tick);
}, [
drawDiscs,
drawLines,
drawParticles,
moveDiscs,
moveParticles
]);
const init = __rspack_external_react.useCallback(()=>{
setSize();
setDiscs();
setLines();
setParticles();
}, [
setDiscs,
setLines,
setParticles,
setSize
]);
__rspack_external_react.useEffect(()=>{
const canvas = canvasRef.current;
if (!canvas) return;
init();
tick();
const handleResize = ()=>{
setSize();
setDiscs();
setLines();
setParticles();
};
globalThis.window.addEventListener("resize", handleResize);
return ()=>{
globalThis.window.removeEventListener("resize", handleResize);
globalThis.cancelAnimationFrame(animationFrameIdRef.current);
};
}, [
init,
setDiscs,
setLines,
setParticles,
setSize,
tick
]);
return /*#__PURE__*/ jsxs("div", {
className: cn(hole_background_module.root, className),
children: [
children,
/*#__PURE__*/ jsx("canvas", {
ref: canvasRef,
className: hole_background_module.canvas,
...props
}),
/*#__PURE__*/ jsx(motion.div, {
"aria-hidden": "true",
className: hole_background_module.glow,
animate: {
backgroundPosition: "0% 300%"
},
transition: {
duration: 5,
ease: "linear",
repeat: 1 / 0
}
}),
/*#__PURE__*/ jsx("div", {
"aria-hidden": "true",
className: hole_background_module.scanlines
})
]
});
});
HoleBackground.displayName = "HoleBackground";
export { HoleBackground };
//# sourceMappingURL=hole-background.js.map