홈 / Skills / 웹 및 UI 디자인 / 애니메이션 만들기 Animate
웹 및 UI 디자인

애니메이션 만들기 Animate

Emil KowalskiMITSkill 언어: 영어
라이선스 확인됨스크립트 없음낮은 위험
실행 예시실제 실행 · claude-sonnet-5-5 · 2026년 10월
I'm building a React app with Tailwind and Motion is not installed. When the user clicks the profile avatar in the header, a small dropdown menu (Profile, Settings, Log out) opens below it. Please build the open and close animation for that dropdown. Give me the code.
Skill: animate
Read: RECIPES.md
import { useEffect, useRef, useState } from "react";

export function ProfileMenu({ src }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    if (!open) return;
    const onPointerDown = (e) => {
      if (!ref.current?.contains(e.target)) setOpen(false);
    };
    const onKeyDown = (e) => e.key === "Escape" && setOpen(false);
    document.addEventListener("pointerdown", onPointerDown);
    document.addEventListener("keydown", onKeyDown);
    return () => {
      document.removeEventListener("pointerdown", onPointerDown);
      document.removeEventListener("keydown", onKeyDown);
    };
  }, [open]);

  return (
    <div ref={ref} className="relative">
      <button
        onClick={() => setOpen((o) => !o)}
        aria-haspopup="menu"
        aria-expanded={open}
        className="rounded-full transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-95"
      >
        <img src={src} alt="Profile" className="size-9 rounded-full" />
      </button>

      <div
        role="menu"
        data-state={open ? "open" : "closed"}
        className={[
          "absolute right-0 top-full mt-2 w-44 origin-top-right rounded-lg border bg-white p-1 shadow-lg",
          "transition-[opacity,transform,visibility] ease-[cubic-bezier(0.23,1,0.32,1)]",
          open
            ? "visible scale-100 opacity-100 duration-200"
            : "invisible pointer-events-none scale-95 opacity-0 duration-150",
          // reduced motion: keep the fade, drop the scale
          "motion-reduce:scale-100",
        ].join(" ")}
      >
        {["Profile", "Settings", "Log out"].map((label) => (
          <button
            key={label}
            role="menuitem"
            tabIndex={open ? 0 : -1}
            onClick={() => setOpen(false)}
            className="block w-full rounded-md px-3 py-2 text-left text-sm hover:bg-gray-100"
          >
            {label}
          </button>
        ))}
      </div>
    </div>
  );
}

If you'd rather use a gated hover, swap hover:bg-gray-100 for [@media(hover:hover)]:hover:bg-gray-100. The color change is subtle enough that I left it plain.

Gate: An avatar click happens occasionally and isn't keyboard-driven, so a standard animation is fine. The purpose is spatial consistency: the menu should look like it came out of the avatar.

Ingredients:

  • Tool: CSS transitions toggled by state. The menu stays mounted, so no exit-animation library is needed. Transitions also retarget if the user double-clicks, where keyframes would restart.
  • Properties: opacity and transform: scale(0.95 → 1), never scale(0). visibility flips at the end of the transition, which keeps the closed menu out of the tab order and screen readers without cutting off the exit.
  • Origin: origin-top-right, assuming the avatar sits at the right edge of the header. Use origin-top-left if it's on the left.
  • Curve and timing: cubic-bezier(0.23, 1, 0.32, 1), 200ms to open and 150ms to close. Closing is a bit faster because the user has already decided.
  • Reduced motion: motion-reduce:scale-100 drops the scale and keeps the fade.

Feel-check: Play it at 3–5× duration in the DevTools animation inspector. Confirm the menu grows out of the avatar corner and that rapid open/close clicks reverse smoothly.

This is hand-rolled, so it lacks arrow-key navigation and focus return to the avatar. If you want those, a headless primitive like Radix or Base UI gives you them, and the same classes work on its data-[state] attributes.

Test run: a profile dropdown in React and Tailwind with no animation library installed. The model read the Skill's RECIPES.md, chose CSS transitions and gave the gate, ingredients and a feel-check. The code was not run in a browser. The model noted that the hand-rolled menu lacks arrow-key navigation and focus return.

하는 일

'움직임을 넣어 줘'라는 요청을 엄격한 검토를 견딜 구현으로 바꿉니다. 정해진 순서로 판단합니다. 먼저 사용 빈도 관문(하루에 수백 번 쓰는 것에는 애니메이션 없음), 다음으로 분명한 목적(피드백, 공간 연속성, 상태, 갑작스러운 변화 방지, 설명, 즐거움), 가장 손쉬운 도구(CSS 트랜지션, @starting-style, CSS 애니메이션, WAAPI, Motion), 속성(transform과 opacity만, 0에서 확대 금지), 이어서 고정된 표에서 이징과 시간 또는 스프링, 중단과 퇴장 동작, 모션 줄이기와 호버 제어입니다. '출시하면 안 되는 것' 표와 버튼, 드롭다운, 툴팁, 모달, 드로어, 토스트, 아코디언, 탭 표시줄 같은 흔한 경우의 RECIPES 파일도 있습니다.

결과물

코드, 그리고 관문 결과, 사용한 요소, 감각으로 확인할 점에 대한 몇 줄.

이런 때 좋습니다

컴포넌트에 움직임을 넣고 싶고, 선택지 목록이 아니라 분명한 결론이 필요한 개발자.

참고 사항 및 위험

낮은 위험:지침만 있는 파일로 스크립트가 없고, 네트워크에 접속하지 않으며, 파일을 쓰지 않습니다. 프런트엔드 코드를 쓸 뿐 실행하지 않으므로 브라우저와 실제 기기에서 확인하세요. React와 Tailwind의 프로필 드롭다운으로 한 번 시험했고, 자체 RECIPES를 읽고 CSS 트랜지션을 골라 관문 결과, 요소, 감각 확인 항목을 줬으며, 모델은 손으로 만든 메뉴에 화살표 키 이동과 포커스 복귀가 없다고 짚었습니다. 질문 없이 호출하면 인사만 합니다. 작성자 Emil Kowalski, MIT.

SKILL.md 원본패키지에 파일 3개