HoverGallery | CoUI
LogoCoUI

HoverGallery

호버 시 이미지를 순서대로 전환하며 보여주는 갤러리 컴포넌트

HoverGallery#

마우스를 올리면 호버 위치에 따라 이미지가 전환되어 표시되는 갤러리 컴포넌트입니다. 제품 목록, 포트폴리오 카드 등에서 미리 보기 효과를 줄 때 활용합니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 이커머스 제품 목록에서 호버 시 여러 각도의 제품 이미지를 미리 볼 때
  • 포트폴리오 그리드에서 프로젝트의 스크린샷을 순서대로 보여줄 때
  • 갤러리나 카탈로그에서 클릭 없이 빠른 이미지 미리 보기를 제공할 때

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

  • Carousel: 명시적인 탐색(화살표, 인디케이터)과 함께 이미지를 전환할 때
  • Card: 호버 없이 단일 이미지를 카드 형태로 표시할 때

기본 사용법 (Basic Usage)#

// URL 리스트로 간편 생성
HoverGallery(
  imageUrls: [
    'https://example.com/product-1.jpg',
    'https://example.com/product-2.jpg',
    'https://example.com/product-3.jpg',
  ],
)

// 커스텀 위젯 빌더 (색상 컨테이너, SVG 등)
final scheme = Theme.of(context).colorScheme;
final fills = [scheme.primary, scheme.secondary, scheme.tertiary];

HoverGallery(
  itemCount: 3,
  itemBuilder: (index) => ColoredBox(color: fills[index].toValue()),
)
// URL 리스트로 간편 생성
HoverGallery(
  imageUrls: [
    'https://example.com/product-1.jpg',
    'https://example.com/product-2.jpg',
    'https://example.com/product-3.jpg',
  ],
)

// 커스텀 컴포넌트 빌더
HoverGallery(
  itemCount: 3,
  itemBuilder: (index) => div(
    [],
    styles: Styles(raw: {
      'background': ['blue', 'green', 'orange'][index],
      'width': '100%',
      'height': '100%',
    }),
  ),
)

빠른 오버라이드 (Chain)#

이미 만든 HoverGallery 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다. .radius24처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius24 == CoreRadius.radius24) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.

class HoverGalleryChainExample extends StatefulWidget {
  const HoverGalleryChainExample({super.key});

  @override
  State<HoverGalleryChainExample> createState() => _HoverGalleryChainExampleState();
}

class _HoverGalleryChainExampleState extends State<HoverGalleryChainExample> {
  @override
  Widget build(BuildContext context) {
    return HoverGallery(
          imageUrls: [
            'https://picsum.photos/seed/a/400/300',
            'https://picsum.photos/seed/b/400/300',
            'https://picsum.photos/seed/c/400/300',
          ],
        )
        .withStyle(
          const CoreHoverGalleryStyle(
            height: CoreSpace.space256,
            animationDuration: Duration(milliseconds: CoreDuration.fast),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
          ),
        )
        .radius16;
  }
}
class HoverGalleryChainExample extends StatefulComponent {
  const HoverGalleryChainExample({super.key});

  @override
  State<HoverGalleryChainExample> createState() => _HoverGalleryChainExampleState();
}

class _HoverGalleryChainExampleState extends State<HoverGalleryChainExample> {
  @override
  Component build(BuildContext context) {
    return HoverGallery(
          imageUrls: [
            'https://picsum.photos/seed/a/400/300',
            'https://picsum.photos/seed/b/400/300',
            'https://picsum.photos/seed/c/400/300',
          ],
        )
        .withStyle(
          const CoreHoverGalleryStyle(
            height: CoreSpace.space256,
            animationDuration: Duration(milliseconds: CoreDuration.fast),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
          ),
        )
        .radius16;
  }
}

Props / Parameters#

속성타입기본값설명
imageUrls List<String>? null 표시할 이미지 URL 목록
itemBuilder Widget/Component Function(int)? null 커스텀 위젯 빌더
itemCount int? null 빌더 모드 시 아이템 수
fit BoxFit (Flutter) / String? (Web) BoxFit.cover / null'cover' 이미지 채우기 방식 (imageUrls 모드 전용). Web 은 CSS object-fit 값 문자열
hoverGalleryStyle CoreHoverGalleryStyle? null 높이·보더 라운드·전환 duration 단일 진입점

imageUrlsitemBuilder + itemCount 중 하나는 반드시 넘겨야 합니다. Flutter 는 생성자 assert 로 debug 빌드에서 즉시 잡아주지만 Web 에는 assert 가 없고, 둘 다 생략하면 양 플랫폼 모두 예외 없이 빈 컨테이너만 렌더합니다 (height 만큼의 빈 영역 — Flutter 도 release 빌드에서는 같습니다).

스타일 시스템 (Style System)#

모든 chrome / 치수 / 애니메이션 override 는 hoverGalleryStyle 슬롯 하나로 흐릅니다.

CoreHoverGalleryStyle 필드#

필드타입설명
height double? Container height override (logical px, pre-scaling).
borderRadius CoreBorderRadius? Container border radius override (pre-scaling).
animationDuration Duration? Image transition animation duration override.

Resolve chain#

CoreHoverGalleryStyle.defaultX (static const)
  → CoreHoverGalleryTheme.style                   // 프로젝트 공통
  → widget.hoverGalleryStyle                      // 인스턴스별

동작 스펙 (Behavior)#

인터랙션#

  • 호버 이동: 마우스 X 좌표에 따라 갤러리 영역을 이미지 수만큼 분할, 해당 영역의 이미지를 표시
  • 클릭/탭: 탭한 위치의 이미지로 전환
  • 호버 없음: 첫 번째 이미지만 표시

애니메이션#

  • Flutter: AnimatedSwitcher로 crossfade (300ms)
  • Web: CSS opacity transition (300ms)

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

Do#

동일한 비율의 이미지 사용

HoverGallery(
  imageUrls: product.images, // 모두 동일 비율 권장
  hoverGalleryStyle: const CoreHoverGalleryStyle(height: CoreSpace.space200),
)

이미지 비율이 다르면 전환 시 시각적으로 불안정합니다. 동일한 비율의 이미지를 사용하세요.

Don't#

너무 많은 이미지 사용 금지

// 20개의 이미지 — 호버로 탐색하기 어려움
HoverGallery(
  imageUrls: product.allImages, // 20장 이상
)

3~6개를 권장합니다.

접근성 (Accessibility)#

키보드 인터랙션#

현재 키보드 조작은 구현되지 않았습니다 — 이미지 전환은 포인터 입력(호버 이동, 탭)으로만 동작합니다.

  • 갤러리 영역은 포커스를 받지 않습니다. Flutter 는 MouseRegion + GestureDetector 만 쓰고 Focus 가 없으며, Web 루트 <div> 에는 tabindexkeydown 핸들러도 없어 Tab 순서에 들어가지 않습니다.

그래서 키보드 사용자에게는 첫 번째 이미지만 보입니다. HoverGallery 는 보조적인 미리 보기로만 쓰고, 전체 이미지에 도달하는 경로를 함께 두세요 (예: 카드를 눌러 Carousel 이 있는 상세 화면으로 이동).

스크린 리더#

  • Flutter: 구현에 Semantics 가 없습니다 — 이미지 설명이 필요하면 호출자가 Semantics 로 감싸세요.
  • Web: imageUrls 모드에서 각 <img>alt 이 자동으로 붙습니다 (CouiLocalizations.galleryImageLabel(n) — 활성 로케일 문자열, 예: 갤러리 이미지 1). itemBuilder 모드에서는 호출자가 직접 넣어야 합니다.

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

항목FlutterWeb
클래스명HoverGalleryHoverGallery
호버 감지 MouseRegion + LayoutBuilder mousemove 이벤트
이미지 렌더링 Image.network(fit: BoxFit.cover) img + inline object-fit: cover
전환 애니메이션 AnimatedSwitcher crossfade CSS opacity transition
  • Carousel: 명시적 탐색 컨트롤이 있는 이미지 슬라이드
  • Card: HoverGallery를 감싸 카드 레이아웃 구성에 사용
  • DotIndicator: HoverGallery 아래에 현재 이미지 위치 표시