Tips & Tricks

How to Implement Skeleton Loading: Claude Code 활용 가이드

implement skeleton loading: Claude Code 활용. 실용적인 코드 예시와 단계별 가이드를 포함합니다.

スケルトンローディングの効果

スケルトンローディング(スケルトンスクリーン)は、콘텐츠로딩中に레이아웃の骨格を표시するUIパターンです。スピナーよりも体感速度が速く感じられ、레이아웃シフトも防げます。Claude Code를 사용하면 再利用可能なスケルトン컴포넌트を빠르게구축할 수 있습니다。

基本のスケルトン컴포넌트

> 汎用的なスケルトン컴포넌트を作って。
> テキスト、이미지、カードの3パターンに대응して。
interface SkeletonProps {
  width?: string | number;
  height?: string | number;
  variant?: 'text' | 'circular' | 'rectangular';
  className?: string;
  lines?: number;
}

function Skeleton({ width, height, variant = 'text', className = '', lines = 1 }: SkeletonProps) {
  const baseClass = 'animate-pulse bg-gray-200 dark:bg-gray-700';

  const variantClass = {
    text: 'rounded',
    circular: 'rounded-full',
    rectangular: 'rounded-lg',
  }[variant];

  if (variant === 'text' && lines > 1) {
    return (
      <div className={`space-y-2 ${className}`}>
        {Array.from({ length: lines }).map((_, i) => (
          <div
            key={i}
            className={`${baseClass} rounded h-4`}
            style={{ width: i === lines - 1 ? '75%' : '100%' }}
          />
        ))}
      </div>
    );
  }

  return (
    <div
      className={`${baseClass} ${variantClass} ${className}`}
      style={{
        width: width ?? (variant === 'text' ? '100%' : undefined),
        height: height ?? (variant === 'text' ? '1rem' : undefined),
      }}
      role="status"
      aria-label="読み込み中"
    />
  );
}

カード타입スケルトン

function CardSkeleton() {
  return (
    <div className="border rounded-lg p-4 space-y-4" aria-busy="true" aria-label="読み込み中">
      <Skeleton variant="rectangular" height={200} />
      <Skeleton variant="text" width="60%" height={24} />
      <Skeleton variant="text" lines={3} />
      <div className="flex items-center gap-3">
        <Skeleton variant="circular" width={40} height={40} />
        <div className="flex-1">
          <Skeleton variant="text" width="40%" />
          <Skeleton variant="text" width="25%" className="mt-1" />
        </div>
      </div>
    </div>
  );
}

function CardListSkeleton({ count = 3 }: { count?: number }) {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
      {Array.from({ length: count }).map((_, i) => (
        <CardSkeleton key={i} />
      ))}
    </div>
  );
}

콘텐츠とスケルトンの전환

interface AsyncContentProps<T> {
  data: T | undefined;
  loading: boolean;
  skeleton: React.ReactNode;
  children: (data: T) => React.ReactNode;
  error?: Error | null;
}

function AsyncContent<T>({ data, loading, skeleton, children, error }: AsyncContentProps<T>) {
  if (error) {
    return <div role="alert" className="text-red-500">エラーが発生しました</div>;
  }

  if (loading || !data) {
    return <>{skeleton}</>;
  }

  return <>{children(data)}</>;
}

// Usage example
function UserProfile() {
  const { data, loading, error } = useQuery('/api/user');

  return (
    <AsyncContent
      data={data}
      loading={loading}
      error={error}
      skeleton={<ProfileSkeleton />}
    >
      {(user) => (
        <div>
          <img src={user.avatar} alt={user.name} />
          <h2>{user.name}</h2>
          <p>{user.bio}</p>
        </div>
      )}
    </AsyncContent>
  );
}

CSS애니메이션の최적화

/* パルスアニメーション */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

.animate-pulse {
  animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* シマーエフェクト(よりリッチな表現) */
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.animate-shimmer {
  background: linear-gradient(
    90deg,
    #e5e7eb 25%,
    #f3f4f6 50%,
    #e5e7eb 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
}

정리

Claude Code를 활용하면 汎用スケルトン컴포넌트から用途別のパターン、シマーエフェクトまで효율적으로구축할 수 있습니다。성능全般에 대해서는성능최적화を、이미지로딩の최적화は이미지지연 로딩를 참고하세요.

スケルトンスクリーンのUX에 대해서는Nielsen Norman Group - Skeleton Screensが参考になります。

#Claude Code #skeleton #ローディング #React #UX