Sliding Ease
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 SlidingEaseVerticalBarsProps {6backgroundColor?: string7lineColor?: string8barColor?: string9lineWidth?: number10animationSpeed?: number11removeWaveLine?: boolean12}1314const SlidingEaseVerticalBars = ({15backgroundColor = "#F0EEE6",16lineColor = "#444",17barColor = "#5E5D59",18lineWidth = 1,19animationSpeed = 0.005,20removeWaveLine = true,21}: SlidingEaseVerticalBarsProps) => {22const canvasRef = useRef<HTMLCanvasElement>(null)23const timeRef = useRef<number>(0)24const animationFrameId = useRef<number | null>(null)25const mouseRef = useRef({ x: 0, y: 0, isDown: false })26const transitionBursts = useRef<Array<{ x: number; y: number; time: number; intensity: number }>>([])27const dprRef = useRef<number>(1)2829interface Bar {30y: number31height: number32width: number33}3435const noise = (x: number, y: number, t: number): number => {36const n = Math.sin(x * 0.02 + t) * Math.cos(y * 0.02 + t) + Math.sin(x * 0.03 - t) * Math.cos(y * 0.01 + t)37return (n + 1) / 238}3940const getMouseInfluence = (x: number, y: number): number => {41const dx = x - mouseRef.current.x42const dy = y - mouseRef.current.y43const distance = Math.sqrt(dx * dx + dy * dy)44const maxDistance = 18045return Math.max(0, 1 - distance / maxDistance)46}4748const getTransitionBurstInfluence = (x: number, y: number, currentTime: number): number => {49let totalInfluence = 05051transitionBursts.current.forEach((burst) => {52const age = currentTime - burst.time53const maxAge = 250054if (age < maxAge) {55const dx = x - burst.x56const dy = y - burst.y57const distance = Math.sqrt(dx * dx + dy * dy)58const burstRadius = (age / maxAge) * 30059const burstWidth = 6060if (Math.abs(distance - burstRadius) < burstWidth) {61const burstStrength = (1 - age / maxAge) * burst.intensity62const proximityToBurst = 1 - Math.abs(distance - burstRadius) / burstWidth63totalInfluence += burstStrength * proximityToBurst64}65}66})6768return Math.min(totalInfluence, 1.5)69}7071const generatePattern = (seed: number, width: number, height: number, numLines: number): Bar[][] => {72const pattern: Bar[][] = []73const lineSpacing = width / numLines7475for (let i = 0; i < numLines; i++) {76const lineBars: Bar[] = []77let currentY = 07879while (currentY < height) {80const noiseVal = noise(i * lineSpacing, currentY, seed)81if (noiseVal > 0.5) {82const barLength = 10 + noiseVal * 3083const barWidth = 2 + noiseVal * 384lineBars.push({85y: currentY + barLength / 2,86height: barLength,87width: barWidth,88})89currentY += barLength + 1590} else {91currentY += 1592}93}94pattern.push(lineBars)95}9697return pattern98}99100const resizeCanvas = useCallback(() => {101const canvas = canvasRef.current102if (!canvas) return103104const dpr = window.devicePixelRatio || 1105dprRef.current = dpr106107const displayWidth = window.innerWidth108const displayHeight = window.innerHeight109110// Set the actual size in memory (scaled up for high DPI)111canvas.width = displayWidth * dpr112canvas.height = displayHeight * dpr113114// Scale the canvas back down using CSS115canvas.style.width = displayWidth + "px"116canvas.style.height = displayHeight + "px"117118// Scale the drawing context so everything draws at the correct size119const ctx = canvas.getContext("2d")120if (ctx) {121ctx.scale(dpr, dpr)122}123}, [])124125const handleMouseMove = useCallback((e: MouseEvent) => {126const canvas = canvasRef.current127if (!canvas) return128129const rect = canvas.getBoundingClientRect()130mouseRef.current.x = e.clientX - rect.left131mouseRef.current.y = e.clientY - rect.top132}, [])133134const handleMouseDown = useCallback((e: MouseEvent) => {135mouseRef.current.isDown = true136const canvas = canvasRef.current137if (!canvas) return138139const rect = canvas.getBoundingClientRect()140const x = e.clientX - rect.left141const y = e.clientY - rect.top142143transitionBursts.current.push({144x,145y,146time: Date.now(),147intensity: 2,148})149150const now = Date.now()151transitionBursts.current = transitionBursts.current.filter((burst) => now - burst.time < 2500)152}, [])153154const handleMouseUp = useCallback(() => {155mouseRef.current.isDown = false156}, [])157158const animate = useCallback(() => {159const canvas = canvasRef.current160if (!canvas) return161162const ctx = canvas.getContext("2d")163if (!ctx) return164165const currentTime = Date.now()166timeRef.current += animationSpeed167168// Use CSS pixel dimensions for calculations169const width = canvas.clientWidth170const height = canvas.clientHeight171172const numLines = Math.floor(width / 15)173const lineSpacing = width / numLines174175// Generate patterns176const pattern1 = generatePattern(0, width, height, numLines)177const pattern2 = generatePattern(5, width, height, numLines)178179// Create cycle with mouse influence180const baseCycleTime = timeRef.current % (Math.PI * 2)181const mouseInfluenceOnCycle = getMouseInfluence(width / 2, height / 2) * 0.5182183let easingFactor: number184const adjustedCycleTime = baseCycleTime + mouseInfluenceOnCycle185186if (adjustedCycleTime < Math.PI * 0.1) {187easingFactor = 0188} else if (adjustedCycleTime < Math.PI * 0.9) {189const transitionProgress = (adjustedCycleTime - Math.PI * 0.1) / (Math.PI * 0.8)190easingFactor = transitionProgress191} else if (adjustedCycleTime < Math.PI * 1.1) {192easingFactor = 1193} else if (adjustedCycleTime < Math.PI * 1.9) {194const transitionProgress = (adjustedCycleTime - Math.PI * 1.1) / (Math.PI * 0.8)195easingFactor = 1 - transitionProgress196} else {197easingFactor = 0198}199200const smoothEasing =201easingFactor < 0.5 ? 4 * easingFactor * easingFactor * easingFactor : 1 - Math.pow(-2 * easingFactor + 2, 3) / 2202203ctx.fillStyle = backgroundColor204ctx.fillRect(0, 0, width, height)205206// Draw lines and interpolated bars207for (let i = 0; i < numLines; i++) {208const x = i * lineSpacing + lineSpacing / 2209const lineMouseInfluence = getMouseInfluence(x, height / 2)210211// Draw vertical line with mouse influence212ctx.beginPath()213ctx.strokeStyle = lineColor214ctx.lineWidth = lineWidth + lineMouseInfluence * 2215ctx.moveTo(x, 0)216ctx.lineTo(x, height)217ctx.stroke()218219// Interpolate between patterns220const bars1 = pattern1[i] || []221const bars2 = pattern2[i] || []222const maxBars = Math.max(bars1.length, bars2.length)223224for (let j = 0; j < maxBars; j++) {225let bar1 = bars1[j]226let bar2 = bars2[j]227228if (!bar1) bar1 = { y: bar2.y - 100, height: 0, width: 0 }229if (!bar2) bar2 = { y: bar1.y + 100, height: 0, width: 0 }230231const barMouseInfluence = getMouseInfluence(x, bar1.y)232const burstInfluence = getTransitionBurstInfluence(x, bar1.y, currentTime)233234// Enhanced wave motion with mouse and burst influence235const baseWaveOffset =236Math.sin(i * 0.3 + j * 0.5 + timeRef.current * 2) * 10 * (smoothEasing * (1 - smoothEasing) * 4)237238const mouseWaveOffset = barMouseInfluence * Math.sin(timeRef.current * 3 + i * 0.2) * 15239const burstWaveOffset = burstInfluence * Math.sin(timeRef.current * 4 + j * 0.3) * 20240const totalWaveOffset = baseWaveOffset + mouseWaveOffset + burstWaveOffset241242// Interpolate properties243const y = bar1.y + (bar2.y - bar1.y) * smoothEasing + totalWaveOffset244const height =245bar1.height + (bar2.height - bar1.height) * smoothEasing + barMouseInfluence * 5 + burstInfluence * 8246const width = bar1.width + (bar2.width - bar1.width) * smoothEasing + barMouseInfluence * 2 + burstInfluence * 3247248// Draw bar with enhanced effects249if (height > 0.1 && width > 0.1) {250const intensity = Math.min(1, 0.8 + barMouseInfluence * 0.2 + burstInfluence * 0.3)251const red = Number.parseInt(barColor.slice(1, 3), 16)252const green = Number.parseInt(barColor.slice(3, 5), 16)253const blue = Number.parseInt(barColor.slice(5, 7), 16)254255ctx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${intensity})`256ctx.fillRect(x - width / 2, y - height / 2, width, height)257}258}259}260261// Draw transition burst effects262if (!removeWaveLine) {263transitionBursts.current.forEach((burst) => {264const age = currentTime - burst.time265const maxAge = 2500266if (age < maxAge) {267const progress = age / maxAge268const radius = progress * 300269const alpha = (1 - progress) * 0.2 * burst.intensity270271ctx.beginPath()272ctx.strokeStyle = `rgba(100, 100, 100, ${alpha})`273ctx.lineWidth = 2274ctx.arc(burst.x, burst.y, radius, 0, 2 * Math.PI)275ctx.stroke()276}277})278}279280animationFrameId.current = requestAnimationFrame(animate)281}, [backgroundColor, lineColor, removeWaveLine, barColor, lineWidth, animationSpeed])282283useEffect(() => {284const canvas = canvasRef.current285if (!canvas) return286287resizeCanvas()288289const handleResize = () => resizeCanvas()290291window.addEventListener("resize", handleResize)292canvas.addEventListener("mousemove", handleMouseMove)293canvas.addEventListener("mousedown", handleMouseDown)294canvas.addEventListener("mouseup", handleMouseUp)295296animate()297298return () => {299window.removeEventListener("resize", handleResize)300canvas.removeEventListener("mousemove", handleMouseMove)301canvas.removeEventListener("mousedown", handleMouseDown)302canvas.removeEventListener("mouseup", handleMouseUp)303304if (animationFrameId.current) {305cancelAnimationFrame(animationFrameId.current)306animationFrameId.current = null307}308timeRef.current = 0309transitionBursts.current = []310}311}, [animate, resizeCanvas, handleMouseMove, handleMouseDown, handleMouseUp])312313return (314<div className="absolute inset-0 w-full h-full overflow-hidden" style={{ backgroundColor }}>315<canvas ref={canvasRef} className="block w-full h-full" />316</div>317)318}319320export default SlidingEaseVerticalBars321
Prop | Type | Default | Description |
---|---|---|---|
backgroundColor | string | '#F0EEE6' | Background color of the canvas. |
lineColor | string | '#444' | Color of the vertical lines. |
barColor | string | '#5E5D59' | Color of the sliding bars. |
lineWidth | number | 1 | Width of the vertical lines. |
animationSpeed | number | 0.005 | Speed of the sliding animation. |
removeWaveLine | boolean | true | Whether to remove the animated wave line (if true, the wave is not shown). |