Advanced (업데이트: 2026. 6. 2.)

Claude Code로 고급 CSS 애니메이션 구현하기: 성능, 접근성, 검증

Claude Code로 CSS 애니메이션을 구현할 때 성능, 접근성, 토큰, Playwright 검증까지 다룹니다.

Claude Code로 고급 CSS 애니메이션 구현하기: 성능, 접근성, 검증

CSS 애니메이션은 움직임보다 의도가 중요하다

Claude Code에게 카드가 부드럽게 나타나게 해 달라고 말하면 코드는 금방 나옵니다. 하지만 실제 서비스에서 중요한 것은 화려함이 아니라 사용자가 무엇이 바뀌었는지 이해하는 것입니다. 좋은 애니메이션은 빠르고, 읽기를 방해하지 않으며, 움직임을 줄이고 싶은 사용자의 설정을 존중하고, 디자인 시스템 안에서 오래 유지될 수 있어야 합니다.

이 글은 구현 관점에서 고급 CSS 애니메이션을 정리합니다. transition과 keyframes의 차이, transformopacity를 우선해야 하는 이유, layout thrash의 의미, prefers-reduced-motion 대응, 스크롤 연동 및 진입 애니메이션, 스켈레톤 로딩, 디자인 토큰, Claude Code 리뷰 프롬프트, Playwright 시각 검증까지 다룹니다.

용어부터 간단히 정리하겠습니다. transition은 hover나 open처럼 두 상태 사이를 부드럽게 이어 주는 방식입니다. keyframes는 0%, 60%, 100%처럼 중간 단계를 지정하는 시간표입니다. layout thrash는 브라우저가 요소의 크기와 위치를 반복해서 다시 계산하는 상황입니다. 모션 토큰은 --motion-duration-fast처럼 애니메이션의 시간, easing, 거리 값을 이름으로 관리하는 값입니다.

함께 보면 좋은 글은 Claude Code 성능 최적화, Claude Code CSS 변수, Claude Code 접근성 구현입니다.

transition과 keyframes를 구분해서 사용하기

transition은 hover, focus, selected, expanded 같은 작은 상태 변화에 적합합니다. keyframes는 진입 애니메이션, 로딩 루프, 단계별 알림, 스크롤 진행률처럼 중간 흐름을 설계해야 할 때 적합합니다.

Claude Code에 요청할 때 이 기준을 먼저 알려 주면 결과가 안정됩니다. 단순히 “더 자연스럽게 움직여 줘”라고 하면 top, left, height, margin을 애니메이션하는 코드가 섞일 수 있습니다. 이런 속성은 레이아웃 재계산을 만들기 쉬워 화면이 끊겨 보입니다.

:root {
  --motion-duration-fast: 160ms;
  --motion-duration-normal: 280ms;
  --motion-ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --motion-distance-sm: 12px;
}

.button {
  transition:
    background-color var(--motion-duration-fast) var(--motion-ease-standard),
    transform var(--motion-duration-fast) var(--motion-ease-standard);
}

.button:hover {
  transform: translateY(-2px);
}

.notice {
  opacity: 0;
  transform: translateY(var(--motion-distance-sm));
  animation: notice-enter var(--motion-duration-normal) var(--motion-ease-standard) forwards;
}

@keyframes notice-enter {
  from {
    opacity: 0;
    transform: translateY(var(--motion-distance-sm));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

버튼은 상태 변화이므로 transition을 사용하고, 알림은 진입 흐름이 필요하므로 keyframes를 사용합니다. 공식 문법은 MDN의 CSS animationstransform 문서를 확인하면 됩니다.

성능은 transform과 opacity에서 시작한다

브라우저는 한 프레임을 만들 때 레이아웃, 페인트, 합성 과정을 거칩니다. 레이아웃은 크기와 위치를 계산하고, 페인트는 색과 그림자를 그리며, 합성은 레이어를 겹칩니다. transformopacity는 합성 단계에서 처리되기 쉬워 대체로 부드럽습니다.

반대로 width, height, top, left, margin을 자주 움직이면 주변 요소까지 영향을 받습니다. 이것이 layout thrash의 흔한 원인입니다. React 컴포넌트에서도 애니메이션 클래스는 단순하게 유지하는 편이 좋습니다.

import type { ReactNode } from "react";
import "./motion.css";

type AnimatedPanelProps = {
  children: ReactNode;
  isOpen: boolean;
};

export function AnimatedPanel({ children, isOpen }: AnimatedPanelProps) {
  return (
    <section
      className={isOpen ? "panel panel-enter" : "panel"}
      aria-hidden={!isOpen}
    >
      {children}
    </section>
  );
}
.panel {
  opacity: 0;
  transform: translateY(12px) scale(0.98);
  pointer-events: none;
}

.panel-enter {
  animation: panel-enter 220ms cubic-bezier(0.2, 0, 0, 1) forwards;
  pointer-events: auto;
}

@keyframes panel-enter {
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

will-change는 필요할 때만 사용해야 합니다. 항상 많은 요소에 붙이면 메모리를 낭비할 수 있습니다. web.dev의 animation performance guide는 어떤 속성을 움직여야 하는지 판단할 때 좋은 기준이 됩니다.

실제로 쓸 만한 유스케이스

첫 번째는 카드 목록, 가격표, 대시보드 위젯의 진입 애니메이션입니다. 짧은 등장은 시선을 안내하지만, 모든 카드에 긴 지연을 주면 읽기 시작이 늦어집니다. 중요한 영역에만 작게 적용하고, 애니메이션이 없어도 내용은 보여야 합니다.

두 번째는 스크롤 연동입니다. 읽기 진행률 바나 섹션 reveal은 사용자에게 현재 위치를 알려 줍니다. 다만 animation-timeline은 지원 여부가 다를 수 있으므로 @supports로 감싸고, 미지원 환경에서는 정적인 상태로 보여야 합니다.

.scroll-progress {
  position: fixed;
  inset: 0 auto auto 0;
  z-index: 20;
  width: 100%;
  height: 3px;
  transform-origin: left;
  transform: scaleX(0);
  background: var(--color-accent, #2563eb);
}

@supports (animation-timeline: scroll()) {
  .scroll-progress {
    animation: progress-grow linear both;
    animation-timeline: scroll();
  }
}

@keyframes progress-grow {
  to {
    transform: scaleX(1);
  }
}

.reveal {
  opacity: 1;
  transform: none;
}

@supports (animation-timeline: view()) {
  .reveal {
    opacity: 0;
    transform: translateY(16px);
    animation: reveal-up 1ms linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 30%;
  }
}

@keyframes reveal-up {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

세 번째는 스켈레톤 로딩입니다. 빈 화면보다 사용자가 기다리는 이유를 이해하기 쉽습니다. 그러나 강한 shimmer를 오래 반복하면 오히려 피곤합니다. 몇 초 이상 걸리는 작업에는 진행 텍스트, 재시도, 취소 버튼도 함께 제공해야 합니다.

네 번째는 SaaS 관리 화면의 피드백입니다. 저장 완료, 필터 적용, 항목 이동, 오류 패널 열림은 짧은 움직임으로 충분합니다. 반복해서 사용하는 업무 화면에서는 안정감이 우선입니다.

reduced motion과 애니메이션하지 않는 판단

prefers-reduced-motion은 사용자가 움직임을 줄이고 싶다고 설정했는지 CSS에서 확인하는 기능입니다. 강한 모션은 일부 사용자에게 불편함이나 피로를 줄 수 있으므로 반드시 대응해야 합니다. 공식 설명은 MDN의 prefers-reduced-motion을 참고하세요.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }

  .scroll-progress {
    animation: none;
    transform: scaleX(1);
  }

  .skeleton {
    animation: none;
    background-image: none;
  }
}

애니메이션하지 않는 편이 좋은 곳도 있습니다. 결제 확인, 법적 안내, 중요한 폼 오류, 긴 본문, 조밀한 데이터 테이블, 하루에 여러 번 여는 메뉴가 대표적입니다. 이곳에서는 즉시성과 안정성이 재미보다 중요합니다.

Claude Code를 장식 담당이 아니라 리뷰어로 쓰기

Claude Code에는 효과 추가뿐 아니라 위험 점검을 요청해야 합니다. 다음 프롬프트를 그대로 사용할 수 있습니다.

Review and improve the CSS animation implementation for this UI.

Requirements:
- Use transition for simple hover/focus/open states.
- Use keyframes only when intermediate timing matters.
- Animate transform and opacity first; avoid top, left, width, height, and margin animations.
- Add design tokens for duration, easing, and distance.
- Respect prefers-reduced-motion.
- Keep content visible when scroll-linked animation is unsupported.
- Do not animate critical form errors, payment confirmation, or long reading content.

Return:
1. Risky selectors and why they are risky.
2. A corrected CSS/React implementation.
3. Manual and Playwright checks to verify overflow and reduced motion.

이렇게 요청하면 layout thrash, 무한 반복, 과도한 will-change, fallback 누락, 사용자 모션 설정 무시 같은 문제를 더 쉽게 찾을 수 있습니다.

Playwright로 눈에 보이는 문제 검증하기

애니메이션 버그는 시각적인 경우가 많아 단위 테스트만으로 부족합니다. Playwright로 가로 스크롤, 콘텐츠 표시 여부, reduced motion 동작을 확인합니다.

import { expect, test } from "@playwright/test";

test("animated page has no horizontal overflow", async ({ page }) => {
  await page.goto("/animation-demo");
  const overflow = await page.evaluate(() => {
    return document.documentElement.scrollWidth > window.innerWidth;
  });
  expect(overflow).toBe(false);
});

test("reduced motion keeps content visible", async ({ browser }) => {
  const context = await browser.newContext({ reducedMotion: "reduce" });
  const page = await context.newPage();
  await page.goto("/animation-demo");

  await expect(page.locator(".reveal").first()).toBeVisible();
  await expect(page.locator(".skeleton").first()).toHaveCSS("animation-name", "none");

  await context.close();
});

중요한 화면에는 데스크톱과 모바일 스크린샷 비교도 추가합니다. 테스트가 디자인 감각을 대신하지는 않지만, 숨은 콘텐츠와 가로 오버플로, reduced motion 누락은 빠르게 잡아 줍니다.

정리와 다음 단계

운영 환경의 CSS 애니메이션은 목적에서 시작합니다. 작은 상태 변화는 transition, 시간표가 필요한 움직임은 keyframes, 이동과 표시 변화는 transformopacity, 일관성은 디자인 토큰, 접근성은 reduced motion, 검증은 Playwright로 잡습니다. Claude Code는 이런 제약을 명확히 전달할 때 가장 좋은 결과를 냅니다.

다음 단계로 기존 카드 목록이나 CTA 영역 하나를 골라 이 글의 토큰, 진입 애니메이션, reduced motion 규칙, Playwright 체크를 작게 적용해 보세요. 더 체계적인 구현 리뷰가 필요하면 트레이닝과 상담을 확인할 수 있습니다.

직접 확인한 결과, transformopacity 중심의 진입 애니메이션은 가로 스크롤을 만들지 않았고, Playwright의 reduced motion 환경에서도 .reveal 콘텐츠가 보이는 상태로 유지되었습니다. 반면 강한 shimmer는 업무 화면에서 대기 시간을 더 길게 느끼게 해 정적인 플레이스홀더가 더 적합했습니다.

#Claude Code #CSS 애니메이션 #View Transitions #스크롤 #performance
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.