react-gradient-animation
Version:
A highly customizable, animated gradient background component for React applications.
148 lines (147 loc) • 5.84 kB
JavaScript
import { debounce, hexToRgb, isValidBlendingMode, Particle } from "./index";
import React, { useEffect, useRef } from "react";
const GradientBackground = ({ count = 12, size = { min: 1000, max: 1200, pulse: 0.1 }, speed = {
x: { min: 0.6, max: 3 },
y: { min: 0.6, max: 3 }
}, colors = {
background: "transparent",
particles: ["#ff681c", "#87ddfe", "#231efe"],
}, blending = "overlay", opacity = { center: 0.45, edge: 0 }, skew = -1.5, shapes: initialShapes = ["c"], className = "", translateYcorrection = true, style = {}, onLoaded, }) => {
const canvasRef = useRef(null);
const animationRef = useRef(null);
const particlesRef = useRef([]);
const configRef = useRef({
count,
size,
speed,
colors,
blending,
opacity,
skew,
shapes: initialShapes,
c: { w: 0, h: 0 },
});
// Utility to detect mobile devices
const isMobile = () => {
if (typeof window === "undefined")
return false;
return /Mobi|Android/i.test(window.navigator.userAgent);
};
// Compute styles before render
const computedStyles = React.useMemo(() => {
const defaultWidth = 1920;
const canvasWidth = configRef.current.c.w ||
(typeof window !== "undefined" ? window.innerWidth : defaultWidth);
const translateY = translateYcorrection
? `translateY(-${Math.ceil(Math.tan((Math.abs(skew) * Math.PI) / 180) * (canvasWidth / 2))}px)`
: "";
const skewValue = `skewY(${skew}deg) ${translateY}`;
return Object.assign({ transform: skewValue, WebkitTransform: skewValue, position: "absolute", top: "0", left: "0", width: "100%", height: "100%", zIndex: -1, pointerEvents: "none", backgroundColor: colors.background }, style);
}, [skew, translateYcorrection, colors.background, style]);
const initParticles = () => {
const { count, colors, shapes, opacity } = configRef.current;
particlesRef.current = [];
for (let i = 0; i < count; i++) {
const color = hexToRgb(colors.particles[i % colors.particles.length]);
const shape = shapes[i % shapes.length];
particlesRef.current.push(new Particle(color, shape, configRef.current, opacity));
}
};
const animate = (ctx, canvasWidth, canvasHeight) => {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
particlesRef.current.forEach((particle) => {
particle.update(canvasWidth, canvasHeight);
particle.draw(ctx, configRef.current.blending);
});
animationRef.current = window.requestAnimationFrame(() => animate(ctx, canvasWidth, canvasHeight));
};
const adjustCanvasSize = (canvas, ctx) => {
const parent = canvas.parentElement;
if (!parent)
return;
const dpr = window.devicePixelRatio || 1;
configRef.current.c.w = parent.clientWidth;
configRef.current.c.h = parent.clientHeight;
canvas.width = configRef.current.c.w * dpr;
canvas.height = configRef.current.c.h * dpr;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
};
const handleResize = () => {
const canvas = canvasRef.current;
if (canvas) {
const ctx = canvas.getContext("2d");
if (!ctx)
return;
if (animationRef.current) {
window.cancelAnimationFrame(animationRef.current);
}
adjustCanvasSize(canvas, ctx);
initParticles();
const { c } = configRef.current;
animate(ctx, c.w, c.h);
}
};
useEffect(() => {
// Adjust particle count and speed for mobile devices
if (isMobile()) {
configRef.current.count = Math.floor(count / 2);
configRef.current.speed = {
x: { min: speed.x.min * 0.5, max: speed.x.max * 0.5 },
y: { min: speed.y.min * 0.5, max: speed.y.max * 0.5 },
};
}
else {
configRef.current.count = count;
configRef.current.speed = speed;
}
configRef.current = Object.assign(Object.assign({}, configRef.current), { size,
colors,
blending,
opacity,
skew, shapes: initialShapes });
const canvas = canvasRef.current;
if (!canvas)
return;
const ctx = canvas.getContext("2d");
if (!ctx)
return;
adjustCanvasSize(canvas, ctx);
ctx.globalCompositeOperation = isValidBlendingMode(blending)
? blending
: "source-over";
initParticles();
animate(ctx, configRef.current.c.w, configRef.current.c.h);
const debouncedHandleResize = debounce(handleResize, 200);
window.addEventListener("resize", debouncedHandleResize);
const handleVisibilityChange = () => {
if (!document.hidden) {
handleResize();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
if (onLoaded) {
onLoaded();
}
return () => {
window.removeEventListener("resize", debouncedHandleResize);
document.removeEventListener("visibilitychange", handleVisibilityChange);
if (animationRef.current) {
window.cancelAnimationFrame(animationRef.current);
}
};
}, [
count,
size,
speed,
colors,
blending,
opacity,
skew,
initialShapes,
translateYcorrection,
onLoaded,
]);
return (React.createElement("canvas", { ref: canvasRef, className: className, style: computedStyles }));
};
export { GradientBackground };