Verticle Bars Effect
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
1'use client';23import { useEffect, useRef, useCallback } from 'react';45interface VerticalBarsNoiseProps {6backgroundColor?: string;7lineColor?: string;8barColor?: string;9lineWidth?: number;10animationSpeed?: number;11removeWaveLine?: boolean;12}1314const hexToRgb = (hex: string): { r: number; g: number; b: number } => {15const cleanHex = hex.charAt(0) === '#' ? hex.substring(1) : hex;16const r = Number.parseInt(cleanHex.substring(0, 2), 16);17const g = Number.parseInt(cleanHex.substring(2, 4), 16);18const b = Number.parseInt(cleanHex.substring(4, 6), 16);19return { r, g, b };20};2122const VerticalBarsNoise = ({23backgroundColor = '#F0EEE6',24lineColor = '#444',25barColor = '#000000',26lineWidth = 1,27animationSpeed = 0.0005,28removeWaveLine = true,29}: VerticalBarsNoiseProps) => {30const canvasRef = useRef<HTMLCanvasElement>(null);31const timeRef = useRef<number>(0);32const animationFrameId = useRef<number | null>(null);33const mouseRef = useRef({ x: 0, y: 0, isDown: false });34const ripples = useRef<35Array<{ x: number; y: number; time: number; intensity: number }>36>([]);37const dprRef = useRef<number>(1);3839const noise = (x: number, y: number, t: number): number => {40const n =41Math.sin(x * 0.01 + t) * Math.cos(y * 0.01 + t) +42Math.sin(x * 0.015 - t) * Math.cos(y * 0.005 + t);43return (n + 1) / 2;44};4546const getMouseInfluence = (x: number, y: number): number => {47const dx = x - mouseRef.current.x;48const dy = y - mouseRef.current.y;49const distance = Math.sqrt(dx * dx + dy * dy);50const maxDistance = 200;51return Math.max(0, 1 - distance / maxDistance);52};5354const getRippleInfluence = (55x: number,56y: number,57currentTime: number58): number => {59let totalInfluence = 0;60ripples.current.forEach((ripple) => {61const age = currentTime - ripple.time;62const maxAge = 2000;63if (age < maxAge) {64const dx = x - ripple.x;65const dy = y - ripple.y;66const distance = Math.sqrt(dx * dx + dy * dy);67const rippleRadius = (age / maxAge) * 300;68const rippleWidth = 50;69if (Math.abs(distance - rippleRadius) < rippleWidth) {70const rippleStrength = (1 - age / maxAge) * ripple.intensity;71const proximityToRipple =721 - Math.abs(distance - rippleRadius) / rippleWidth;73totalInfluence += rippleStrength * proximityToRipple;74}75}76});77return Math.min(totalInfluence, 2);78};7980const resizeCanvas = useCallback(() => {81const canvas = canvasRef.current;82if (!canvas) return;8384const dpr = window.devicePixelRatio || 1;85dprRef.current = dpr;8687const displayWidth = window.innerWidth;88const displayHeight = window.innerHeight;8990// Set the actual size in memory (scaled up for high DPI)91canvas.width = displayWidth * dpr;92canvas.height = displayHeight * dpr;9394// Scale the canvas back down using CSS95canvas.style.width = displayWidth + 'px';96canvas.style.height = displayHeight + 'px';9798// Scale the drawing context so everything draws at the correct size99const ctx = canvas.getContext('2d');100if (ctx) {101ctx.scale(dpr, dpr);102}103}, []);104105const handleMouseMove = useCallback((e: MouseEvent) => {106const canvas = canvasRef.current;107if (!canvas) return;108109const rect = canvas.getBoundingClientRect();110// No need to multiply by device pixel ratio for mouse coordinates111// since we're working in CSS pixels for the logic112mouseRef.current.x = e.clientX - rect.left;113mouseRef.current.y = e.clientY - rect.top;114}, []);115116const handleMouseDown = useCallback((e: MouseEvent) => {117mouseRef.current.isDown = true;118const canvas = canvasRef.current;119if (!canvas) return;120121const rect = canvas.getBoundingClientRect();122const x = e.clientX - rect.left;123const y = e.clientY - rect.top;124125ripples.current.push({126x,127y,128time: Date.now(),129intensity: 1.5,130});131132const now = Date.now();133ripples.current = ripples.current.filter(134(ripple) => now - ripple.time < 2000135);136}, []);137138const handleMouseUp = useCallback(() => {139mouseRef.current.isDown = false;140}, []);141142const animate = useCallback(() => {143const canvas = canvasRef.current;144if (!canvas) return;145146const ctx = canvas.getContext('2d');147if (!ctx) return;148149timeRef.current += animationSpeed;150const currentTime = Date.now();151152// Use CSS pixel dimensions for calculations153const canvasWidth = canvas.clientWidth;154const canvasHeight = canvas.clientHeight;155156const numLines = Math.floor(canvasHeight / 11);157const lineSpacing = canvasHeight / numLines;158159ctx.fillStyle = backgroundColor;160ctx.fillRect(0, 0, canvasWidth, canvasHeight);161162for (let i = 0; i < numLines; i++) {163const y = i * lineSpacing + lineSpacing / 2;164const mouseInfluence = getMouseInfluence(canvasWidth / 2, y);165const lineAlpha = Math.max(0.3, 0.3 + mouseInfluence * 0.7);166167ctx.beginPath();168const lineRgb = hexToRgb(lineColor);169ctx.strokeStyle = `rgba(${lineRgb.r}, ${lineRgb.g}, ${lineRgb.b}, ${lineAlpha})`;170ctx.lineWidth = lineWidth + mouseInfluence * 2;171ctx.moveTo(0, y);172ctx.lineTo(canvasWidth, y);173ctx.stroke();174175for (let x = 0; x < canvasWidth; x += 8) {176const noiseVal = noise(x, y, timeRef.current);177const mouseInfl = getMouseInfluence(x, y);178const rippleInfl = getRippleInfluence(x, y, currentTime);179const totalInfluence = mouseInfl + rippleInfl;180181const threshold = Math.max(1820.2,1830.5 - mouseInfl * 0.2 - Math.abs(rippleInfl) * 0.1184);185186if (noiseVal > threshold) {187const barWidth = 3 + noiseVal * 10 + totalInfluence * 5;188const barHeight = 2 + noiseVal * 3 + totalInfluence * 3;189190const baseAnimation =191Math.sin(timeRef.current + y * 0.0375) * 20 * noiseVal;192const mouseAnimation = mouseRef.current.isDown193? Math.sin(timeRef.current * 3 + x * 0.01) * 10 * mouseInfl194: 0;195const rippleAnimation =196rippleInfl * Math.sin(timeRef.current * 2 + x * 0.02) * 15;197198const animatedX =199x + baseAnimation + mouseAnimation + rippleAnimation;200201// Color intensity based on influence202const intensity = Math.min(2031,204Math.max(0.7, 0.7 + totalInfluence * 0.3)205);206const barRgb = hexToRgb(barColor);207ctx.fillStyle = `rgba(${barRgb.r}, ${barRgb.g}, ${barRgb.b}, ${intensity})`;208209ctx.fillRect(210animatedX - barWidth / 2,211y - barHeight / 2,212barWidth,213barHeight214);215}216}217}218219// Draw ripple effects220if (!removeWaveLine) {221ripples.current.forEach((ripple) => {222const age = currentTime - ripple.time;223const maxAge = 2000;224if (age < maxAge) {225const progress = age / maxAge;226const radius = progress * 300;227const alpha = (1 - progress) * 0.3 * ripple.intensity;228229ctx.beginPath();230ctx.strokeStyle = `rgba(100, 100, 100, ${alpha})`;231ctx.lineWidth = 2;232ctx.arc(ripple.x, ripple.y, radius, 0, 2 * Math.PI);233ctx.stroke();234}235});236}237238animationFrameId.current = requestAnimationFrame(animate);239}, [240backgroundColor,241lineColor,242removeWaveLine,243barColor,244lineWidth,245animationSpeed,246]);247248useEffect(() => {249const canvas = canvasRef.current;250if (!canvas) return;251252resizeCanvas();253254const handleResize = () => resizeCanvas();255256window.addEventListener('resize', handleResize);257canvas.addEventListener('mousemove', handleMouseMove);258canvas.addEventListener('mousedown', handleMouseDown);259canvas.addEventListener('mouseup', handleMouseUp);260261animate();262263return () => {264window.removeEventListener('resize', handleResize);265canvas.removeEventListener('mousemove', handleMouseMove);266canvas.removeEventListener('mousedown', handleMouseDown);267canvas.removeEventListener('mouseup', handleMouseUp);268269if (animationFrameId.current) {270cancelAnimationFrame(animationFrameId.current);271animationFrameId.current = null;272}273timeRef.current = 0;274ripples.current = [];275};276}, [animate, resizeCanvas, handleMouseMove, handleMouseDown, handleMouseUp]);277278return (279<div280className='absolute inset-0 w-full h-full overflow-hidden'281style={{ backgroundColor }}282>283<canvas ref={canvasRef} className='block w-full h-full' />284</div>285);286};287288export default VerticalBarsNoise;289
Prop | Type | Default | Description |
---|---|---|---|
backgroundColor | string | '#F0EEE6' | Background color of the canvas. |
lineColor | string | '#444' | Color of the vertical lines. |
barColor | string | '#000000' | Color of the animated bars. |
lineWidth | number | 1 | Width of the vertical lines. |
animationSpeed | number | 0.0005 | Speed of the bar animation. |