Tips & Tricks

Claude Codeでスケルトンローディングを実装する方法

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)}</>;
}

// 使用例
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 #スケルトン #ローディング #React #UX