Noise Texture Background

Drifting film-grain overlay that gives any section tactile analog warmth — texture you feel more than see

backgroundnoisegraintextureanalog
Preview

Source Code

"use client";

interface NoiseTextureBackgroundProps {
  children?: React.ReactNode;
  opacity?: number;
  baseFrequency?: number;
  animated?: boolean;
  blend?: React.CSSProperties["mixBlendMode"];
  className?: string;
}

export default function NoiseTextureBackground({
  children,
  opacity = 0.15,
  baseFrequency = 0.8,
  animated = true,
  blend = "soft-light",
  className = "",
}: NoiseTextureBackgroundProps) {
  const noiseUrl = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='${baseFrequency}' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`;

  const css = `
    @keyframes ntb-drift {
      0%, 100% { transform: translate(0, 0); }
      12.5% { transform: translate(-3%, 2%); }
      25% { transform: translate(2%, -4%); }
      37.5% { transform: translate(-4%, -2%); }
      50% { transform: translate(3%, 4%); }
      62.5% { transform: translate(-2%, 3%); }
      75% { transform: translate(4%, -3%); }
      87.5% { transform: translate(-3%, -4%); }
    }
    .ntb-grain {
      will-change: transform;
    }
    @media (prefers-reduced-motion: reduce) {
      .ntb-grain { animation: none !important; }
    }
  `;

  return (
    <div className={`relative overflow-hidden bg-[#12100e] ${className}`}>
      <style>{css}</style>
      <div
        aria-hidden="true"
        className="ntb-grain pointer-events-none absolute -inset-[100%]"
        style={{
          backgroundImage: noiseUrl,
          opacity,
          mixBlendMode: blend,
          animation: animated ? "ntb-drift 0.8s step-end infinite" : undefined,
        }}
      />
      <div className="relative z-10">{children}</div>
    </div>
  );
}