VibeCoderzVibeCoderz
Telegram
All Prompts
animationscroll animation

Gradual Blur

Компонент Gradual Blur: плавное размытие краев контейнера. Идеально для прокрутки, создания глубины, смешивания контента. Настраиваемая сила, направление, экспоненциальное затухание.

by Zhou JasonLive Preview

Prompt

# Gradual Blur

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

~~~/README.md
# Gradual Blur

A production-grade React component that creates a smooth, multi-layered blur effect at the edges of a container. It uses multiple backdrop-filter layers with varying opacity to achieve a seamless gradient blur that CSS simple gradients cannot match.

## Features

- **Directional**: Supports top, bottom, left, right positioning.
- **Configurable Falloff**: Control the blur strength, curve (linear, bezier, exponential), and layer count.
- **Responsive**: Adjusts dimensions based on breakpoints.
- **Performance**: Uses `backdrop-filter` efficiently with optimized layer counts.

## Usage

```tsx
import GradualBlur from '@/sd-components/ef26f0f2-37d1-409d-8dcc-4c71d85b1447';

function MyComponent() {
  return (
    <div className="relative h-[500px] overflow-hidden">
      <div className="overflow-y-auto h-full p-4">
        {/* Scrollable content */}
      </div>
      
      <GradualBlur
        position="bottom"
        height="6rem"
        strength={2}
        divCount={5}
        curve="ease-out"
        exponential={true}
      />
    </div>
  );
}
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | The edge to apply the blur to. |
| `strength` | `number` | `2` | Intensity of the blur effect. |
| `height` | `string` | `'6rem'` | Height of the blur area (for vertical positions). |
| `divCount` | `number` | `5` | Number of blur layers. Higher = smoother. |
| `exponential` | `boolean` | `false` | If true, uses exponential blur scaling for smoother falloff. |
| `curve` | `'linear' \| 'bezier' \| 'ease-in' \| ...` | `'linear'` | The easing curve for the gradient opacity. |
| `preset` | `string` | - | Pre-configured settings (e.g., 'subtle', 'intense'). |
~~~

~~~/src/App.tsx
import React from 'react';
import GradualBlur from './Component';

export default function App() {
  return (
    <div className="min-h-screen bg-[#F9F9F9] flex items-center justify-center p-4 sm:p-8 font-sans text-slate-800">
      <div className="relative w-full max-w-md h-[600px] bg-white rounded-2xl shadow-[0_20px_40px_-12px_rgba(0,0,0,0.05)] overflow-hidden border border-slate-100 flex flex-col">
        
        {/* Header */}
        <div className="p-8 pb-4 border-b border-slate-50 shrink-0 z-10 bg-white relative">
          <h2 className="text-2xl font-medium tracking-tight text-slate-900">Gradual Blur</h2>
          <p className="text-slate-500 mt-2 text-sm leading-relaxed">
            Scroll content below to see the seamless fade-out effect.
          </p>
        </div>

        {/* Scrollable Content Container */}
        <div className="relative flex-1 overflow-hidden">
          <div className="h-full overflow-y-auto px-8 py-6 pb-32 space-y-12">
            {[...Array(6)].map((_, i) => (
              <div key={i} className="space-y-3">
                <div className="flex items-center gap-3 mb-2">
                  <div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center text-xs font-medium text-slate-500">
                    0{i + 1}
                  </div>
                  <h3 className="text-sm font-semibold uppercase tracking-wider text-slate-400">Design Concept</h3>
                </div>
                <h4 className="text-lg font-medium text-slate-800">Seamless Transitions</h4>
                <p className="text-slate-600 leading-relaxed text-sm">
                  The gradual blur effect creates a sophisticated transition between the scrollable content and the container edge. Unlike a hard cutoff or a simple transparent gradient, this technique mimics optical depth by progressively blurring the content as it exits the viewport.
                </p>
                <div className="h-24 bg-slate-50 rounded-lg w-full border border-slate-100/50"></div>
              </div>
            ))}
            <div className="h-10"></div> 
          </div>

          {/* The Blur Component - Positioned absolutely at the bottom */}
          <GradualBlur
            position="bottom"
            height="160px"
            strength={2}
            divCount={8} 
            curve="ease-out"
            exponential={true}
            opacity={1}
            zIndex={20}
          />
        </div>

        {/* Floating Action Button */}
        <div className="absolute bottom-8 left-0 right-0 flex justify-center z-30 pointer-events-none">
          <button className="bg-slate-900 text-white px-6 py-3 rounded-full text-sm font-medium shadow-xl hover:bg-slate-800 transition-all hover:scale-105 active:scale-95 pointer-events-auto flex items-center gap-2">
            <span>Explore More</span>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M5 12h14m-7-7 7 7-7 7"/>
            </svg>
          </button>
        </div>

      </div>
    </div>
  );
}
~~~

~~~/package.json
{
  "name": "gradual-blur",
  "description": "A reusable gradual blur component",
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "clsx": "^2.0.0",
    "tailwind-merge": "^2.0.0"
  }
}
~~~

~~~/src/Component.tsx
import React, { CSSProperties, useEffect, useRef, useState, useMemo, PropsWithChildren } from 'react';

type GradualBlurProps = PropsWithChildren<{
  position?: 'top' | 'bottom' | 'left' | 'right';
  strength?: number;
  height?: string;
  width?: string;
  divCount?: number;
  exponential?: boolean;
  zIndex?: number;
  animated?: boolean | 'scroll';
  duration?: string;
  easing?: string;
  opacity?: number;
  curve?: 'linear' | 'bezier' | 'ease-in' | 'ease-out' | 'ease-in-out';
  responsive?: boolean;
  mobileHeight?: string;
  tabletHeight?: string;
  desktopHeight?: string;
  mobileWidth?: string;
  tabletWidth?: string;
  desktopWidth?: string;
  preset?:
    | 'top'
    | 'bottom'
    | 'left'
    | 'right'
    | 'subtle'
    | 'intense'
    | 'smooth'
    | 'sharp'
    | 'header'
    | 'footer'
    | 'sidebar'
    | 'page-header'
    | 'page-footer';
  gpuOptimized?: boolean;
  hoverIntensity?: number;
  target?: 'parent' | 'page';
  onAnimationComplete?: () => void;
  className?: string;
  style?: CSSProperties;
}>;

const DEFAULT_CONFIG: Partial<GradualBlurProps> = {
  position: 'bottom',
  strength: 2,
  height: '6rem',
  divCount: 5,
  exponential: false,
  zIndex: 10,
  animated: false,
  duration: '0.3s',
  easing: 'ease-out',
  opacity: 1,
  curve: 'linear',
  responsive: false,
  target: 'parent',
  className: '',
  style: {}
};

const PRESETS: Record<string, Partial<GradualBlurProps>> = {
  top: { position: 'top', height: '6rem' },
  bottom: { position: 'bottom', height: '6rem' },
  left: { position: 'left', height: '6rem' },
  right: { position: 'right', height: '6rem' },
  subtle: { height: '4rem', strength: 1, opacity: 0.8, divCount: 3 },
  intense: { height: '10rem', strength: 4, divCount: 8, exponential: true },
  smooth: { height: '8rem', curve: 'bezier', divCount: 10 },
  sharp: { height: '5rem', curve: 'linear', divCount: 4 },
  header: { position: 'top', height: '8rem', curve: 'ease-out' },
  footer: { position: 'bottom', height: '8rem', curve: 'ease-out' },
  sidebar: { position: 'left', height: '6rem', strength: 2.5 },
  'page-header': {
    position: 'top',
    height: '10rem',
    target: 'page',
    strength: 3
  },
  'page-footer': {
    position: 'bottom',
    height: '10rem',
    target: 'page',
    strength: 3
  }
};

const CURVE_FUNCTIONS: Record<string, (p: number) => number> = {
  linear: p => p,
  bezier: p => p * p * (3 - 2 * p),
  'ease-in': p => p * p,
  'ease-out': p => 1 - Math.pow(1 - p, 2),
  'ease-in-out': p => (p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2)
};

const mergeConfigs = (...configs: Partial<GradualBlurProps>[]): Partial<GradualBlurProps> => {
  return configs.reduce((acc, config) => ({ ...acc, ...config }), {});
};

const getGradientDirection = (position: string): string => {
  const directions: Record<string, string> = {
    top: 'to top',
    bottom: 'to bottom',
    left: 'to left',
    right: 'to right'
  };
  return directions[position] || 'to bottom';
};

const debounce = <T extends (...a: any[]) => void>(fn: T, wait: number) => {
  let t: ReturnType<typeof setTimeout>;
  return (...a: Parameters<T>) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...a), wait);
  };
};

const useResponsiveDimension = (
  responsive: boolean | undefined,
  config: Partial<GradualBlurProps>,
  key: keyof GradualBlurProps
) => {
  const [val, setVal] = useState<any>(config[key]);
  
  useEffect(() => {
    if (!responsive) return;
    const calc = () => {
      const w = window.innerWidth;
      let v: any = config[key];
      const cap = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
      const k = cap(key as string);
      
      // @ts-ignore
      if (w <= 480 && config['mobile' + k]) v = config['mobile' + k];
      // @ts-ignore
      else if (w <= 768 && config['tablet' + k]) v = config['tablet' + k];
      // @ts-ignore
      else if (w <= 1024 && config['desktop' + k]) v = config['desktop' + k];
      
      setVal(v);
    };
    const deb = debounce(calc, 100);
    calc();
    window.addEventListener('resize', deb);
    return () => window.removeEventListener('resize', deb);
  }, [responsive, config, key]);
  return responsive ? val : (config as any)[key];
};

const useIntersectionObserver = (ref: React.RefObject<HTMLDivElement>, shouldObserve: boolean = false) => {
  const [isVisible, setIsVisible] = useState(!shouldObserve);
  useEffect(() => {
    if (!shouldObserve || !ref.current) return;
    const observer = new IntersectionObserver(([entry]) => setIsVisible(entry.isIntersecting), { threshold: 0.1 });
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, [ref, shouldObserve]);
  return isVisible;
};

/**
 * GradualBlur Component
 * 
 * Creates a smooth, multi-layered blur effect that fades out at the edges.
 * Useful for overlays, scrollable areas, and seamless content transitions.
 */
export function GradualBlur(props: GradualBlurProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isHovered, setIsHovered] = useState(false);
  
  const config = useMemo(() => {
    const presetConfig = props.preset && PRESETS[props.preset] ? PRESETS[props.preset] : {};
    return mergeConfigs(DEFAULT_CONFIG, presetConfig, props) as Required<GradualBlurProps>;
  }, [props]);

  const responsiveHeight = useResponsiveDimension(config.responsive, config, 'height');
  const responsiveWidth = useResponsiveDimension(config.responsive, config, 'width');
  const isVisible = useIntersectionObserver(containerRef, config.animated === 'scroll');

  const blurDivs = useMemo(() => {
    const divs: React.ReactNode[] = [];
    const increment = 100 / config.divCount;
    const currentStrength =
      isHovered && config.hoverIntensity ? config.strength * config.hoverIntensity : config.strength;
    const curveFunc = CURVE_FUNCTIONS[config.curve] || CURVE_FUNCTIONS.linear;

    for (let i = 1; i <= config.divCount; i++) {
      let progress = i / config.divCount;
      progress = curveFunc(progress);
      
      let blurValue: number;
      if (config.exponential) {
        blurValue = Math.pow(2, progress * 4) * 0.0625 * currentStrength;
      } else {
        blurValue = 0.0625 * (progress * config.divCount + 1) * currentStrength;
      }

      const p1 = Math.round((increment * i - increment) * 10) / 10;
      const p2 = Math.round(increment * i * 10) / 10;
      const p3 = Math.round((increment * i + increment) * 10) / 10;
      const p4 = Math.round((increment * i + increment * 2) * 10) / 10;
      
      let gradient = `transparent ${p1}%, black ${p2}%`;
      if (p3 <= 100) gradient += `, black ${p3}%`;
      if (p4 <= 100) gradient += `, transparent ${p4}%`;
      
      const direction = getGradientDirection(config.position);
      
      const divStyle: CSSProperties = {
        maskImage: `linear-gradient(${direction}, ${gradient})`,
        WebkitMaskImage: `linear-gradient(${direction}, ${gradient})`,
        backdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
        WebkitBackdropFilter: `blur(${blurValue.toFixed(3)}rem)`,
        opacity: config.opacity,
        transition:
          config.animated && config.animated !== 'scroll'
            ? `backdrop-filter ${config.duration} ${config.easing}`
            : undefined,
        position: 'absolute',
        inset: 0,
        pointerEvents: 'none',
      };
      
      divs.push(<div key={i} className="gradual-blur-layer" style={divStyle} />);
    }
    return divs;
  }, [config, isHovered]);

  const containerStyle: CSSProperties = useMemo(() => {
    const isVertical = ['top', 'bottom'].includes(config.position);
    const isHorizontal = ['left', 'right'].includes(config.position);
    const isPageTarget = config.target === 'page';
    
    const baseStyle: CSSProperties = {
      position: isPageTarget ? 'fixed' : 'absolute',
      pointerEvents: config.hoverIntensity ? 'auto' : 'none',
      opacity: isVisible ? 1 : 0,
      transition: config.animated ? `opacity ${config.duration} ${config.easing}` : undefined,
      zIndex: isPageTarget ? config.zIndex + 100 : config.zIndex,
      ...config.style
    };

    if (isVertical) {
      baseStyle.height = responsiveHeight;
      baseStyle.width = responsiveWidth || '100%';
      // @ts-ignore
      baseStyle[config.position] = 0;
      baseStyle.left = 0;
      baseStyle.right = 0;
    } else if (isHorizontal) {
      baseStyle.width = responsiveWidth || responsiveHeight;
      baseStyle.height = '100%';
      // @ts-ignore
      baseStyle[config.position] = 0;
      baseStyle.top = 0;
      baseStyle.bottom = 0;
    }
    
    return baseStyle;
  }, [config, responsiveHeight, responsiveWidth, isVisible]);

  const { onAnimationComplete, duration, animated } = config;

  useEffect(() => {
    if (isVisible && animated === 'scroll' && onAnimationComplete) {
      // @ts-ignore
      const t = setTimeout(() => onAnimationComplete(), parseFloat(duration) * 1000);
      return () => clearTimeout(t);
    }
  }, [isVisible, animated, onAnimationComplete, duration]);

  return (
    <div
      ref={containerRef}
      className={`gradual-blur ${config.target === 'page' ? 'gradual-blur-page' : 'gradual-blur-parent'} ${config.className}`}
      style={containerStyle}
      onMouseEnter={config.hoverIntensity ? () => setIsHovered(true) : undefined}
      onMouseLeave={config.hoverIntensity ? () => setIsHovered(false) : undefined}
    >
      <div className="relative w-full h-full">{blurDivs}</div>
      {props.children && <div className="relative">{props.children}</div>}
    </div>
  );
}

export default GradualBlur;
~~~

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