UNPKG

reactbits-mcp-server

Version:

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

191 lines (160 loc) 5.77 kB
import { useRef, useEffect } from 'react'; import * as THREE from 'three'; import './GridDistortion.css'; const vertexShader = ` uniform float time; varying vec2 vUv; varying vec3 vPosition; void main() { vUv = uv; vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`; const fragmentShader = ` uniform sampler2D uDataTexture; uniform sampler2D uTexture; uniform vec4 resolution; varying vec2 vUv; void main() { vec2 uv = vUv; vec4 offset = texture2D(uDataTexture, vUv); gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg); }`; const GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => { const containerRef = useRef(null); const imageAspectRef = useRef(1); const cameraRef = useRef(null); const initialDataRef = useRef(null); useEffect(() => { if (!containerRef.current) return; const container = containerRef.current; const scene = new THREE.Scene(); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(renderer.domElement); const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000); camera.position.z = 2; cameraRef.current = camera; const uniforms = { time: { value: 0 }, resolution: { value: new THREE.Vector4() }, uTexture: { value: null }, uDataTexture: { value: null }, }; const textureLoader = new THREE.TextureLoader(); textureLoader.load(imageSrc, (texture) => { texture.minFilter = THREE.LinearFilter; imageAspectRef.current = texture.image.width / texture.image.height; uniforms.uTexture.value = texture; handleResize(); }); const size = grid; const data = new Float32Array(4 * size * size); for (let i = 0; i < size * size; i++) { data[i * 4] = Math.random() * 255 - 125; data[i * 4 + 1] = Math.random() * 255 - 125; } initialDataRef.current = new Float32Array(data); const dataTexture = new THREE.DataTexture( data, size, size, THREE.RGBAFormat, THREE.FloatType ); dataTexture.needsUpdate = true; uniforms.uDataTexture.value = dataTexture; const material = new THREE.ShaderMaterial({ side: THREE.DoubleSide, uniforms, vertexShader, fragmentShader, }); const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1); const plane = new THREE.Mesh(geometry, material); scene.add(plane); const handleResize = () => { const width = container.offsetWidth; const height = container.offsetHeight; const containerAspect = width / height; const imageAspect = imageAspectRef.current; renderer.setSize(width, height); const scale = Math.max(containerAspect / imageAspect, 1); plane.scale.set(imageAspect * scale, scale, 1); const frustumHeight = 1; const frustumWidth = frustumHeight * containerAspect; camera.left = -frustumWidth / 2; camera.right = frustumWidth / 2; camera.top = frustumHeight / 2; camera.bottom = -frustumHeight / 2; camera.updateProjectionMatrix(); uniforms.resolution.value.set(width, height, 1, 1); }; const mouseState = { x: 0, y: 0, prevX: 0, prevY: 0, vX: 0, vY: 0 }; const handleMouseMove = (e) => { const rect = container.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = 1 - (e.clientY - rect.top) / rect.height; mouseState.vX = x - mouseState.prevX; mouseState.vY = y - mouseState.prevY; Object.assign(mouseState, { x, y, prevX: x, prevY: y }); }; const handleMouseLeave = () => { dataTexture.needsUpdate = true; Object.assign(mouseState, { x: 0, y: 0, prevX: 0, prevY: 0, vX: 0, vY: 0 }); }; container.addEventListener('mousemove', handleMouseMove); container.addEventListener('mouseleave', handleMouseLeave); window.addEventListener('resize', handleResize); handleResize(); const animate = () => { requestAnimationFrame(animate); uniforms.time.value += 0.05; const data = dataTexture.image.data; for (let i = 0; i < size * size; i++) { data[i * 4] *= relaxation; data[i * 4 + 1] *= relaxation; } const gridMouseX = size * mouseState.x; const gridMouseY = size * mouseState.y; const maxDist = size * mouse; for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { const distance = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2); if (distance < maxDist * maxDist) { const index = 4 * (i + size * j); const power = Math.min(maxDist / Math.sqrt(distance), 10); data[index] += strength * 100 * mouseState.vX * power; data[index + 1] -= strength * 100 * mouseState.vY * power; } } } dataTexture.needsUpdate = true; renderer.render(scene, camera); }; animate(); return () => { container.removeEventListener('mousemove', handleMouseMove); container.removeEventListener('mouseleave', handleMouseLeave); window.removeEventListener('resize', handleResize); renderer.dispose(); geometry.dispose(); material.dispose(); dataTexture.dispose(); if (uniforms.uTexture.value) uniforms.uTexture.value.dispose(); }; }, [grid, mouse, strength, relaxation, imageSrc]); return <div ref={containerRef} className={`distortion-container ${className}`} />; }; export default GridDistortion;