MainBlogProjectsDocs
components
snake-beam

Snake Beam

Snake Beam

Grid Background

Snake

Required

Tailwind μ„€μΉ˜ (Optional)

tailwindCSS

components/ui/snake-beam.tsx

import * as React from 'react';
 
export const SnakeBeam = ({ className }: { className?: string }) => {
  const requestIdRef = React.useRef(0);
  const [gradientValues, setGradientValues] = React.useState({
    x1: 250,
    x2: 350,
    y2: 60,
  });
  const [isAnimating, setIsAnimating] = React.useState(true); // μ• λ‹ˆλ©”μ΄μ…˜ μƒνƒœ 관리
 
  React.useEffect(() => {
    const animate = () => {
      // μ• λ‹ˆλ©”μ΄μ…˜μ΄ λλ‚¬λŠ”μ§€ 확인
      if (
        gradientValues.x1 <= -100 &&
        gradientValues.x2 <= 0 &&
        gradientValues.y2 <= 0
      ) {
        // μ• λ‹ˆλ©”μ΄μ…˜μ΄ λλ‚¬μœΌλ©΄ 1초 λ™μ•ˆ λŒ€κΈ°
        if (isAnimating) {
          setIsAnimating(false); // μ• λ‹ˆλ©”μ΄μ…˜ 쀑지
          setTimeout(() => {
            setGradientValues({ x1: 250, x2: 350, y2: 60 }); // κ°’ 리셋
            setIsAnimating(true); // μ• λ‹ˆλ©”μ΄μ…˜ μž¬μ‹œμž‘
          }, 1000); // 1초 λŒ€κΈ°
        }
        return;
      }
      // μ• λ‹ˆλ©”μ΄μ…˜ 계속 진행
      setGradientValues((prev) => ({
        x1: prev.x1 > -100 ? prev.x1 - (250 + 100) * 0.005 : prev.x1,
        x2: prev.x2 > -100 ? prev.x2 - (350 + 100) * 0.005 : prev.x2,
        y2: prev.y2 > 0 ? prev.y2 - 60 * 0.005 : prev.y2,
      }));
      if (isAnimating) {
        requestIdRef.current = requestAnimationFrame(animate);
      }
    };
 
    if (isAnimating) {
      requestIdRef.current = requestAnimationFrame(animate);
    }
 
    return () => {
      cancelAnimationFrame(requestIdRef.current);
    };
  }, [isAnimating, gradientValues.x1, gradientValues.x2, gradientValues.y2]);
 
  return (
    <svg
      className={className}
      width="236"
      height="60"
      viewBox="0 0 236 60"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M0.5 0.5 H90 V30 H150 V60 H235.5" // M(x y): μ‹œμž‘μ§€μ  μ΄ˆκΈ°ν™”, H(x): xμΆ• 이동, V(y): yμΆ• 이동
        stroke="url(#paint0_linear)"
      ></path>
      <defs>
        <linearGradient
          id="paint0_linear"
          gradientUnits="userSpaceOnUse"
          x1={`${gradientValues.x1}`}
          y1="0"
          x2={`${gradientValues.x2}`}
          y2={`${gradientValues.y2}`}
        >
          <stop stopColor="#2EB9DF" stopOpacity="0"></stop>
          <stop stopColor="#2EB9DF"></stop>
          <stop offset="1" stopColor="#9E00FF" stopOpacity="0"></stop>
        </linearGradient>
      </defs>
    </svg>
  );
};

How to customize beam?

<path /> νƒœκ·Έμ—μ„œ d μ†μ„±μœΌλ‘œ Beam의 λͺ¨μ–‘을 λ³€κ²½ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

주석에 μ¨μžˆλ“― 각 숫자λ₯Ό μˆ˜μ •ν•˜μ—¬ Beam λͺ¨μ–‘을 λ°”κΏ€ 수 μžˆμŠ΅λ‹ˆλ‹€.

  • M(x y): μ‹œμž‘μ§€μ  μ΄ˆκΈ°ν™”
  • H(x): xμΆ• 이동
  • V(y): yμΆ• 이동

그렀진 Beam λͺ¨μ–‘에 맞좰 μˆ˜μ •ν•˜κ³  x1, y1, x2, y2λ₯Ό μ‘°μ •ν•˜λ©΄ λ˜λŠ”λ°

ν˜„μž¬ μƒνƒœλŠ” Beam의 길이λ₯Ό μ•½ 100ν”½μ…€λ‘œ 작고 x1을 -100κΉŒμ§€ μ΄λ™μ‹œμΌœ μ‚¬λΌμ§€κ²Œ ν•˜κ³  μžˆμœΌλ‹ˆ,

μ‘°κΈˆμ”© κ±΄λ“œλ €λ³΄λ©΄μ„œ μ»€μŠ€ν…€ ν•˜μ‹œλ©΄ λ©λ‹ˆλ‹€.