VibeCoderzVibeCoderz
All Prompts
animationbackground

Grid Distortion Background

WebGL-эффект искажения сетки для фона с интерактивными волнами. Создает динамичные анимированные фоны для веб-сайтов.

by Zhou JasonLive Preview

Prompt

# Grid Distortion Background

You are given a task to integrate an existing React component in the codebase

~~~/README.md
# GridDistortionBackground

A high-performance WebGL-based grid distortion effect for background images, featuring interactive mouse-driven liquid-like ripples.

## Dependencies

- `react`: ^18.2.0
- `three`: ^0.160.0
- `lucide-react`: ^0.344.0

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `imageSrc` | `string` | **Required** | Source URL for the background image |
| `grid` | `number` | `15` | Number of grid cells (density) |
| `mouse` | `number` | `0.1` | Radius of mouse influence (0 to 1) |
| `strength` | `number` | `0.15` | Strength of the distortion effect |
| `relaxation` | `number` | `0.9` | How quickly the grid returns to normal (0 to 1) |
| `className` | `string` | `""` | Optional CSS class for the container |

## Usage

```tsx
import { GridDistortion } from '@/sd-components/7a22cbc7-e820-4de2-8730-fe82e9e587bb';

function MyPage() {
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <GridDistortion
        imageSrc="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05"
        grid={20}
        mouse={0.2}
        strength={0.15}
        relaxation={0.95}
      />
    </div>
  );
}
```
~~~

~~~/src/App.tsx
/**
 * Demo application for GridDistortionBackground
 */

import React from 'react';
import { GridDistortion } from './Component';
import { RefreshCcw } from 'lucide-react';

export default function App() {
  const [key, setKey] = React.useState(0);

  return (
    <div className="min-h-screen bg-background flex flex-center p-20">
      <div className="w-full max-w-5xl aspect-video relative rounded-3xl overflow-hidden shadow-[0_40px_80px_-20px_rgba(0,0,0,0.05)] border-none">
        <GridDistortion
          key={key}
          imageSrc="https://picsum.photos/1920/1080?grayscale&random=1"
          grid={20}
          mouse={0.15}
          strength={0.2}
          relaxation={0.96}
        />
        
        {/* Floating Label as per Project System Prompt */}
        <div className="absolute top-8 left-8 z-10">
          <h1 className="text-xl font-medium text-foreground/80 tracking-tight">
            Grid Distortion
          </h1>
        </div>

        {/* Reply/Reset Button as per Project System Prompt */}
        <button 
          onClick={() => setKey(prev => prev + 1)}
          className="absolute bottom-8 right-8 z-10 w-12 h-12 flex items-center justify-center bg-background/80 backdrop-blur-md rounded-full border border-border/50 text-foreground/60 hover:text-primary transition-all active:scale-95 shadow-sm"
          title="Refresh Effect"
        >
          <RefreshCcw className="w-5 h-5" />
        </button>
      </div>
    </div>
  );
}
~~~

~~~/package.json
{
  "name": "grid-distortion-background",
  "description": "A high-performance WebGL-based grid distortion effect for background images.",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "three": "^0.160.0",
    "framer-motion": "^11.0.0",
    "lucide-react": "^0.344.0"
  }
}
~~~

~~~/src/Component.tsx
/**
 * GridDistortionBackground Component
 * 
 * A high-performance WebGL-based grid distortion effect.
 * Uses Three.js for rendering and a custom fragment shader to achieve
 * liquid-like ripples when the mouse interacts with the image.
 */

import React, { useRef, useEffect } from 'react';
import * as THREE from 'three';

export interface GridDistortionProps {
  /** Number of grid cells (density) */
  grid?: number;
  /** Radius of mouse influence (0 to 1) */
  mouse?: number;
  /** Strength of the distortion effect */
  strength?: number;
  /** How quickly the grid returns to normal (0 to 1) */
  relaxation?: number;
  /** Source URL for the background image */
  imageSrc: string;
  /** Optional CSS class for the container */
  className?: string;
}

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);
}
`;

export function GridDistortion({
  grid = 15,
  mouse = 0.1,
  strength = 0.15,
  relaxation = 0.9,
  imageSrc,
  className = ''
}: GridDistortionProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const sceneRef = useRef<THREE.Scene | null>(null);
  const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
  const cameraRef = useRef<THREE.OrthographicCamera | null>(null);
  const planeRef = useRef<THREE.Mesh | null>(null);
  const animationIdRef = useRef<number | null>(null);
  const resizeObserverRef = useRef<ResizeObserver | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const container = containerRef.current;
    const scene = new THREE.Scene();
    sceneRef.current = scene;

    const renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true,
      powerPreference: 'high-performance'
    });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setClearColor(0x000000, 0);
    rendererRef.current = renderer;
    
    container.innerHTML = '';
    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 as THREE.Texture | null },
      uDataTexture: { value: null as THREE.DataTexture | null }
    };

    const textureLoader = new THREE.TextureLoader();
    textureLoader.load(imageSrc, texture => {
      texture.minFilter = THREE.LinearFilter;
      texture.magFilter = THREE.LinearFilter;
      texture.wrapS = THREE.ClampToEdgeWrapping;
      texture.wrapT = THREE.ClampToEdgeWrapping;
      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] = 0;
      data[i * 4 + 1] = 0;
    }

    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,
      transparent: true
    });

    const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);
    const plane = new THREE.Mesh(geometry, material);
    planeRef.current = plane;
    scene.add(plane);

    const handleResize = () => {
      if (!container || !renderer || !camera) return;
      const rect = container.getBoundingClientRect();
      const width = rect.width;
      const height = rect.height;
      if (width === 0 || height === 0) return;

      const containerAspect = width / height;
      renderer.setSize(width, height);

      if (plane) {
        plane.scale.set(containerAspect, 1, 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);
    };

    if (window.ResizeObserver) {
      const resizeObserver = new ResizeObserver(() => {
        handleResize();
      });
      resizeObserver.observe(container);
      resizeObserverRef.current = resizeObserver;
    } else {
      window.addEventListener('resize', handleResize);
    }

    const mouseState = {
      x: 0,
      y: 0,
      prevX: 0,
      prevY: 0,
      vX: 0,
      vY: 0
    };

    const handleMouseMove = (e: MouseEvent) => {
      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;
      
      mouseState.x = x;
      mouseState.y = y;
      mouseState.prevX = x;
      mouseState.prevY = y;
    };

    const handleMouseLeave = () => {
      mouseState.x = 0;
      mouseState.y = 0;
      mouseState.prevX = 0;
      mouseState.prevY = 0;
      mouseState.vX = 0;
      mouseState.vY = 0;
    };

    container.addEventListener('mousemove', handleMouseMove);
    container.addEventListener('mouseleave', handleMouseLeave);

    handleResize();

    const animate = () => {
      animationIdRef.current = requestAnimationFrame(animate);
      if (!renderer || !scene || !camera || !dataTexture) return;

      uniforms.time.value += 0.05;

      const dataArr = dataTexture.image.data as Float32Array;
      
      // Decay existing distortion
      for (let i = 0; i < size * size; i++) {
        dataArr[i * 4] *= relaxation;
        dataArr[i * 4 + 1] *= relaxation;
      }

      // Add new distortion from mouse
      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 dx = gridMouseX - i;
          const dy = gridMouseY - j;
          const distSq = dx * dx + dy * dy;
          
          if (distSq < maxDist * maxDist) {
            const index = 4 * (i + size * j);
            const dist = Math.sqrt(distSq);
            const power = Math.max(0, 1 - dist / maxDist);
            
            dataArr[index] += strength * mouseState.vX * power * 10;
            dataArr[index + 1] -= strength * mouseState.vY * power * 10;
          }
        }
      }

      dataTexture.needsUpdate = true;
      renderer.render(scene, camera);
    };

    animate();

    return () => {
      if (animationIdRef.current) {
        cancelAnimationFrame(animationIdRef.current);
      }
      if (resizeObserverRef.current) {
        resizeObserverRef.current.disconnect();
      } else {
        window.removeEventListener('resize', handleResize);
      }
      container.removeEventListener('mousemove', handleMouseMove);
      container.removeEventListener('mouseleave', handleMouseLeave);
      
      if (renderer) {
        renderer.dispose();
        if (container.contains(renderer.domElement)) {
          container.removeChild(renderer.domElement);
        }
      }
      if (planeRef.current) {
        planeRef.current.geometry.dispose();
        (planeRef.current.material as THREE.Material).dispose();
      }
      if (dataTexture) dataTexture.dispose();
      if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();
      
      sceneRef.current = null;
      rendererRef.current = null;
      cameraRef.current = null;
      planeRef.current = null;
    };
  }, [grid, mouse, strength, relaxation, imageSrc]);

  return (
    <div
      ref={containerRef}
      className={`relative overflow-hidden ${className}`}
      style={{
        width: '100%',
        height: '100%',
        minWidth: '0',
        minHeight: '0'
      }}
    />
  );
}

export default GridDistortion;
~~~

Implementation Guidelines

1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system

Help me integrate this into my design
All Prompts