Loading | CoUI

Loading

작업 진행 중을 표시하는 로딩 인디케이터 컴포넌트

Loading#

비동기 작업이나 데이터 로딩 중임을 사용자에게 알리는 로딩 인디케이터 컴포넌트입니다. spinner, dots, bar, pulse 변형을 지원합니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 데이터를 불러오거나 저장하는 중에 사용자에게 진행 중임을 알릴 때
  • 콘텐츠 레이아웃을 예측할 수 없는 비동기 작업 중에 표시할 때
  • 버튼 클릭 후 처리가 완료될 때까지 기다리는 상태를 표시할 때
  • 페이지 전체 로딩 오버레이가 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • Skeleton: 콘텐츠 구조를 미리 알 수 있을 때 (더 나은 사용자 경험)
  • Progress: 작업 완료율을 퍼센트로 표시할 수 있을 때

기본 사용법 (Basic Usage)#

// 기본 스피너
CoLoading()

// 크기 지정
CoLoading(size: 32)

// 색상 지정
CoLoading(
  size: 24,
  color: Colors.blue,
)

// variant 지정
CoLoading(variant: CoreLoadingVariant.dots)

// 레이블 포함 패턴
Column(
  mainAxisSize: MainAxisSize.min,
  children: [
    CoLoading(size: 32),
    SizedBox(height: 8),
    Text('데이터를 불러오는 중...'),
  ],
)
// 기본 스피너
CoLoading()

// 크기 지정 (픽셀 단위)
CoLoading(size: 32)

// 색상 지정
CoLoading(
  size: 24,
  color: Colors.blue,
)

// variant 지정
CoLoading(variant: CoreLoadingVariant.dots)

// 레이블 포함 (flex 레이아웃으로 구성)
div(
  classes: 'flex flex-col items-center gap-2',
  [
    CoLoading(size: 32),
    span(
      [Component.text('데이터를 불러오는 중...')],
      classes: 'text-sm text-muted-foreground',
    ),
  ],
)

Props / Parameters#

속성타입기본값설명
variant CoreLoadingVariant spinner 인디케이터 변형 스타일 (spinner, dots, bar, pulse)
size double? null 인디케이터 크기 (픽셀). componentSize보다 우선.
componentSize CoreComponentSize? null 사이즈 토큰 (sm, md, lg).
color Color? null 인디케이터 색상 (기본값: 테마 primary)

변형 (Variants)#

Spinner#

원형으로 회전하는 기본 스피너입니다.

CoLoading(variant: CoreLoadingVariant.spinner)

Dots#

세 개의 점이 순서대로 강조되는 형태입니다.

CoLoading(variant: CoreLoadingVariant.dots)

Bar#

가로 바가 좌우로 움직이는 형태입니다.

CoLoading(variant: CoreLoadingVariant.bar)

Pulse#

요소가 맥박처럼 커졌다 작아지는 형태입니다.

CoLoading(variant: CoreLoadingVariant.pulse)

크기 (Sizes)#

크기componentSize픽셀
SmallCoreComponentSize.sm16px
MediumCoreComponentSize.md24px
LargeCoreComponentSize.lg40px

size 파라미터로 픽셀 값을 직접 지정할 수도 있습니다.

동작 스펙 (Behavior)#

인터랙션#

  • Loading은 표시 전용 컴포넌트입니다. 인터랙션이 없습니다.
  • 로딩 중에는 배경 컨텐츠 상호작용을 차단하는 오버레이와 함께 사용하는 경우가 많음

상태 전환#

  • 로딩 시작: 즉시 표시 (애니메이션 시작)
  • 로딩 완료: 부모에서 조건부 렌더링으로 제거

애니메이션#

  • Spinner: 360도 회전 1s linear 무한 반복
  • Dots: 각 점이 0.4s 간격으로 페이드인/아웃
  • Bar: 좌우 왕복 1.5s ease-in-out 무한 반복
  • Pulse: 크기 1.0 → 1.3 → 1.0, 1s ease-in-out 무한 반복

사용 가이드라인 (Usage Guidelines)#

✅ Do#

로딩 인디케이터로 진행 중인 작업 안내

Column(
  mainAxisSize: MainAxisSize.min,
  children: [
    CoLoading(variant: CoreLoadingVariant.spinner, size: 40),
    SizedBox(height: 8),
    Text('파일을 업로드하는 중... (2/5)'),
  ],
)

로딩 옆/아래에 텍스트로 어떤 작업이 진행 중인지 알려주면 사용자의 불안감을 줄일 수 있습니다.


❌ Don't#

레이아웃을 예측할 수 있는 경우 Loading 대신 Skeleton 사용

// ❌ 카드 목록 로딩에 단순 스피너 사용
if (isLoading) {
  return Center(child: CoLoading()); // 레이아웃 점프 발생
}
return CardList(items: items);

카드 목록처럼 구조가 예측 가능한 경우 Skeleton을 사용하면 레이아웃 점프 없이 더 나은 경험을 제공합니다.

✅ Do#

버튼 내 인라인 로딩으로 즉각적 피드백 제공

CoButton.primary(
  onPressed: isSubmitting ? null : handleSubmit,
  child: isSubmitting
    ? Row(mainAxisSize: MainAxisSize.min, children: [
        CoLoading(size: 16, color: Colors.white),
        SizedBox(width: 8),
        Text('제출 중...'),
      ])
    : Text('제출하기'),
)

버튼 내 인라인 로딩은 사용자에게 클릭이 처리되고 있음을 즉각적으로 알려주고 중복 클릭을 방지합니다.


❌ Don't#

페이지 전체에 로딩 오버레이를 과도하게 사용 금지

// ❌ 작은 데이터 업데이트에 전체 페이지 오버레이
Stack(
  children: [
    MainContent(),
    if (isLoading) FullPageLoadingOverlay(), // 단순 항목 추가에 전체 블로킹
  ],
)

작은 업데이트에 전체 페이지를 블로킹하면 사용성이 크게 저하됩니다. 변경되는 영역에만 로딩을 표시하세요.

✅ Do#

로딩 시간이 길 때 진행률(Progress)을 함께 표시하세요.

// 진행률을 알 수 있을 때는 Progress 컴포넌트 사용
CoProgress(value: uploadProgress) // 0.0 ~ 1.0

진행률을 보여주면 사용자가 얼마나 기다려야 하는지 알 수 있어 이탈률을 낮출 수 있습니다.


❌ Don't#

짧은 작업(1초 미만)에 로딩 인디케이터를 표시하지 마세요.

// ❌ 매우 빠른 작업에도 로딩 스피너 표시
FutureBuilder(
  future: fastLocalOperation(),  // 50ms 작업
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CoLoading();  // 깜빡임 현상 발생
    }
    return content;
  },
)

너무 짧은 작업에 로딩을 보여주면 UI가 깜빡이는 플래시 효과가 발생합니다. 300ms 이상 걸리는 작업에만 표시하세요.

접근성 (Accessibility)#

키보드 인터랙션#

해당 없음. Loading은 인터랙티브 요소가 아닙니다.

스크린 리더#

  • Flutter: Semantics(label: '로딩 중', liveRegion: true) 적용
  • Web: role="status", aria-live="polite", aria-label="로딩 중" 자동 적용

터치 타겟#

해당 없음. 표시 전용 요소.

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명CoLoadingCoLoading
Spinner 구현 CustomPainter 기반 자체 회전 CSS animation: spin
Pulse 구현 AnimationController + Transform.scale CSS @keyframes scale
색상 기본값 Theme.of(context).colorScheme.primary var(--coui-primary)
  • Skeleton: 콘텐츠 구조를 예측할 수 있을 때 더 나은 로딩 표시자
  • Progress: 진행률을 퍼센트로 표시할 때
  • EmptyState: 로딩 완료 후 데이터가 없을 때

조합 예제#

// Loading + 조건부 렌더링 + EmptyState 패턴
Widget buildContent() {
  return AnimatedSwitcher(
    duration: Duration(milliseconds: 200),
    child: isLoading
      ? Center(key: Key('loading'), child: CoLoading())
      : items.isEmpty
        ? CoEmptyState(
            key: Key('empty'),
            icon: Icon(Icons.search_off, size: 48),
            title: '결과가 없습니다',
          )
        : ListView.builder(
            key: Key('list'),
            itemCount: items.length,
            itemBuilder: (context, index) => ItemTile(item: items[index]),
          ),
  );
}