UNPKG

reactbits-mcp-server

Version:

MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements

159 lines (140 loc) 4.52 kB
import { useEffect, useRef } from "react"; import { gsap } from "gsap"; const DecayCard = ({ width = 300, height = 400, image = 'https://picsum.photos/300/400?grayscale', children, }) => { const svgRef = useRef(null); const displacementMapRef = useRef(null); const cursor = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const cachedCursor = useRef({ ...cursor.current }); const winsize = useRef({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const lerp = (a, b, n) => (1 - n) * a + n * b; const map = (x, a, b, c, d) => ((x - a) * (d - c)) / (b - a) + c; const distance = (x1, x2, y1, y2) => Math.hypot(x1 - x2, y1 - y2); const handleResize = () => { winsize.current = { width: window.innerWidth, height: window.innerHeight }; }; const handleMouseMove = (ev) => { cursor.current = { x: ev.clientX, y: ev.clientY }; }; window.addEventListener("resize", handleResize); window.addEventListener("mousemove", handleMouseMove); const imgValues = { imgTransforms: { x: 0, y: 0, rz: 0 }, displacementScale: 0, }; const render = () => { let targetX = lerp( imgValues.imgTransforms.x, map(cursor.current.x, 0, winsize.current.width, -120, 120), 0.1 ); let targetY = lerp( imgValues.imgTransforms.y, map(cursor.current.y, 0, winsize.current.height, -120, 120), 0.1 ); let targetRz = lerp( imgValues.imgTransforms.rz, map(cursor.current.x, 0, winsize.current.width, -10, 10), 0.1 ); const bound = 50; if (targetX > bound) targetX = bound + (targetX - bound) * 0.2; if (targetX < -bound) targetX = -bound + (targetX + bound) * 0.2; if (targetY > bound) targetY = bound + (targetY - bound) * 0.2; if (targetY < -bound) targetY = -bound + (targetY + bound) * 0.2; imgValues.imgTransforms.x = targetX; imgValues.imgTransforms.y = targetY; imgValues.imgTransforms.rz = targetRz; if (svgRef.current) { gsap.set(svgRef.current, { x: imgValues.imgTransforms.x, y: imgValues.imgTransforms.y, rotateZ: imgValues.imgTransforms.rz, }); } const cursorTravelledDistance = distance( cachedCursor.current.x, cursor.current.x, cachedCursor.current.y, cursor.current.y ); imgValues.displacementScale = lerp( imgValues.displacementScale, map(cursorTravelledDistance, 0, 200, 0, 400), 0.06 ); if (displacementMapRef.current) { gsap.set(displacementMapRef.current, { attr: { scale: imgValues.displacementScale } }); } cachedCursor.current = { ...cursor.current }; requestAnimationFrame(render); }; render(); return () => { window.removeEventListener("resize", handleResize); window.removeEventListener("mousemove", handleMouseMove); }; }, []); return ( <div ref={svgRef} className="relative" style={{ width: `${width}px`, height: `${height}px` }} > <svg viewBox="-60 -75 720 900" preserveAspectRatio="xMidYMid slice" className="relative w-full h-full block [will-change:transform]" > <filter id="imgFilter"> <feTurbulence type="turbulence" baseFrequency="0.015" numOctaves="5" seed="4" stitchTiles="stitch" x="0%" y="0%" width="100%" height="100%" result="turbulence1" /> <feDisplacementMap ref={displacementMapRef} in="SourceGraphic" in2="turbulence1" scale="0" xChannelSelector="R" yChannelSelector="B" x="0%" y="0%" width="100%" height="100%" result="displacementMap3" /> </filter> <g> <image href={image} x="0" y="0" width="600" height="750" filter="url(#imgFilter)" preserveAspectRatio="xMidYMid slice" /> </g> </svg> <div className="absolute bottom-[1.2em] left-[1em] tracking-[-0.5px] font-black text-[2.5rem] leading-[1.5em] first-line:text-[6rem]"> {children} </div> </div> ); }; export default DecayCard;