Docs

Text Pressure

Pressure Animation For Text !!

Run the following command

It will create a new file text-pressure.tsx inside the components/mage-ui/text directory.

mkdir -p components/mage-ui/text && touch components/mage-ui/text/text-pressure.tsx

Paste the code

Open the newly created file and paste the following code:

 
"use client";
import React, { useEffect, useRef, useState } from 'react';
 
type TextPressureProps = {
  text?: string;
  fontFamily?: string;
  fontUrl?: string;
  width?: boolean;
  weight?: boolean;
  italic?: boolean;
  alpha?: boolean;
  flex?: boolean;
  stroke?: boolean;
  scale?: boolean;
  textColor?: string;
  strokeColor?: string;
  strokeWidth?: number;
  className?: string;
  minFontSize?: number;
};
 
const TextPressure: React.FC<TextPressureProps> = ({
  text = '<Mage UI>',
  fontFamily = 'Compressa VF',
  fontUrl = 'https://res.cloudinary.com/dr6lvwubh/raw/upload/v1529908256/CompressaPRO-GX.woff2',
  width = true,
  weight = true,
  italic = true,
  alpha = false,
  flex = true,
  stroke = false,
  scale = false,
  textColor = 'text-black dark:text-white',
  strokeColor = '#FF0000',
  strokeWidth = 2,
  className = '',
  minFontSize = 24,
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const titleRef = useRef<HTMLHeadingElement>(null);
  const spansRef = useRef<(HTMLSpanElement | null)[]>([]);
 
  const mouseRef = useRef({ x: 0, y: 0 });
  const cursorRef = useRef({ x: 0, y: 0 });
 
  const [fontSize, setFontSize] = useState(minFontSize);
  const [scaleY, setScaleY] = useState(1);
  const [lineHeight, setLineHeight] = useState(1);
 
  const chars = text.split('');
 
  const dist = (a: { x: number; y: number }, b: { x: number; y: number }) => {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    return Math.sqrt(dx * dx + dy * dy);
  };
 
  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      cursorRef.current.x = e.clientX;
      cursorRef.current.y = e.clientY;
    };
    const handleTouchMove = (e: TouchEvent) => {
      const t = e.touches[0];
      cursorRef.current.x = t.clientX;
      cursorRef.current.y = t.clientY;
    };
 
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('touchmove', handleTouchMove, { passive: false });
 
    if (containerRef.current) {
      const { left, top, width, height } = containerRef.current.getBoundingClientRect();
      mouseRef.current.x = left + width / 2;
      mouseRef.current.y = top + height / 2;
      cursorRef.current.x = mouseRef.current.x;
      cursorRef.current.y = mouseRef.current.y;
    }
 
    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('touchmove', handleTouchMove);
    };
  }, []);
 
  const setSize = () => {
    if (!containerRef.current || !titleRef.current) return;
 
    const { width: containerW, height: containerH } = containerRef.current.getBoundingClientRect();
 
    let newFontSize = containerW / (chars.length / 2);
    newFontSize = Math.max(newFontSize, minFontSize);
 
    setFontSize(newFontSize);
    setScaleY(1);
    setLineHeight(1);
 
    requestAnimationFrame(() => {
      if (!titleRef.current) return;
      const textRect = titleRef.current.getBoundingClientRect();
 
      if (scale && textRect.height > 0) {
        const yRatio = containerH / textRect.height;
        setScaleY(yRatio);
        setLineHeight(yRatio);
      }
    });
  };
 
  useEffect(() => {
    setSize();
    window.addEventListener('resize', setSize);
    return () => window.removeEventListener('resize', setSize);
  }, [scale, text]);
 
  useEffect(() => {
    let rafId: number;
    const animate = () => {
      mouseRef.current.x += (cursorRef.current.x - mouseRef.current.x) / 15;
      mouseRef.current.y += (cursorRef.current.y - mouseRef.current.y) / 15;
 
      if (titleRef.current) {
        const titleRect = titleRef.current.getBoundingClientRect();
        const maxDist = titleRect.width / 2;
 
        spansRef.current.forEach((span) => {
          if (!span) return;
 
          const rect = span.getBoundingClientRect();
          const charCenter = {
            x: rect.x + rect.width / 2,
            y: rect.y + rect.height / 2,
          };
 
          const d = dist(mouseRef.current, charCenter);
 
          const getAttr = (distance: number, minVal: number, maxVal: number) => {
            const val = maxVal - Math.abs((maxVal * distance) / maxDist);
            return Math.max(minVal, val + minVal);
          };
 
          const wdth = width ? Math.floor(getAttr(d, 5, 200)) : 100;
          const wght = weight ? Math.floor(getAttr(d, 100, 900)) : 400;
          const italVal = italic ? getAttr(d, 0, 1).toFixed(2) : '0';
          const alphaVal = alpha ? getAttr(d, 0, 1).toFixed(2) : '1';
 
          span.style.opacity = alphaVal;
          span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal}`;
        });
      }
 
      rafId = requestAnimationFrame(animate);
    };
 
    animate();
    return () => cancelAnimationFrame(rafId);
  }, [width, weight, italic, alpha, chars.length]);
 
  // Add @font-face for custom font
  useEffect(() => {
    const fontFace = `
      @font-face {
        font-family: '${fontFamily}';
        src: url('${fontUrl}') format('woff2');
      }
    `;
    const styleSheet = document.createElement('style');
    styleSheet.type = 'text/css';
    styleSheet.innerText = fontFace;
    document.head.appendChild(styleSheet);
 
    return () => {
      document.head.removeChild(styleSheet);
    };
  }, [fontFamily, fontUrl]);
 
  return (
    <div
      ref={containerRef}
      className="w-[90vw] h-screen flex justify-center items-center mx-auto"
    >
      <h1
        ref={titleRef}
        className={`${textColor} ${className}`}
        style={{
          fontFamily: fontFamily,
          fontSize: `${fontSize}px`,
          transform: `scaleY(${scaleY})`,
          lineHeight: `${lineHeight}`,
          display: 'flex',
          margin: 0,
        }}
      >
        {chars.map((char, index) => (
          <span
            key={index}
            ref={(el) => {
              spansRef.current[index] = el;
            }}
            style={{
              display: 'inline-block',
              fontVariationSettings: "'wght' 400, 'wdth' 100, 'ital' 0",
              fontFamily: fontFamily,
            }}
          >
            {char}
          </span>
        ))}
      </h1>
    </div>
  );
};
 
export default TextPressure;