Hover | CoUI
LogoCoUI

Hover

호버 상태를 추적하고 호버 시각 효과를 적용하는 래퍼 컴포넌트

Hover#

자식 위젯을 감싸 두 가지 기능을 제공하는 래퍼 컴포넌트입니다.

  1. 호버 상태 추적onHover / onEnter / onExit 콜백으로 포인터 호버 상태를 보고합니다.
  2. 호버 시각 효과 — 포인터가 위에 있는 동안 effect(scale / fade / brighten / shadow)를 애니메이션합니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 카드/이미지에 호버 시 확대·페이드·그림자 효과를 주고 싶을 때
  • 포인터 호버 진입/이탈을 콜백으로 추적해야 할 때

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

  • HoverCard: 호버 시 떠오르는 정보 패널이 필요할 때
  • Tooltip: 호버 시 간단한 도움말 텍스트를 띄울 때

기본 사용법 (Basic Usage)#

Hover(
  effect: CoreHoverEffect.scaleUp,
  child: Card(child: const Text('Hover me')),
)
Hover(
  effect: CoreHoverEffect.scaleUp,
  child: Card(child: const Text('Hover me')),
)

빠른 오버라이드 (Chain)#

이미 만든 Hover 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class HoverChainExample extends StatelessWidget {
  const HoverChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Hover(
      effect: CoreHoverEffect.scaleUp,
      child: Card(child: const Text('Hover me')),
    ).withStyle(
      const CoreHoverStyle(
        scaleUpFactor: 1.15,
        effectDuration: Duration(milliseconds: CoreDuration.moderate),
        waitDuration: Duration(milliseconds: CoreDuration.fast),
        shadowBoxShadow: CoreShadow.xl,
        shadowBaseColor: CoreColor.token(CoreColors.primary),
      ),
    );
  }
}
class HoverChainExample extends StatelessComponent {
  const HoverChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return Hover(
      effect: CoreHoverEffect.scaleUp,
      child: Card(child: const Text('Hover me')),
    ).withStyle(
      const CoreHoverStyle(
        scaleUpFactor: 1.15,
        effectDuration: Duration(milliseconds: CoreDuration.moderate),
        waitDuration: Duration(milliseconds: CoreDuration.fast),
        shadowBoxShadow: CoreShadow.xl,
        shadowBaseColor: CoreColor.token(CoreColors.primary),
      ),
    );
  }
}

Props / Parameters#

속성타입기본값설명
child Widget? / Component? null 호버 영역으로 감쌀 콘텐츠
effect CoreHoverEffect CoreHoverEffect.defaultEffect (none) 호버 시각 효과 (none, scaleUp, scaleDown, fade, brighten, shadow)
onHover ValueChanged<bool>? null 해소된 호버 상태 변경 콜백
onEnter void Function()? null 포인터 진입 즉시 호출
onExit void Function()? null 포인터 이탈 즉시 호출
waitDuration Duration? null500ms (defaultWaitDuration) onHover(true) 발화 전 대기 시간
showDuration Duration? null200ms (defaultShowDuration) 이탈 후 onHover(false) 발화 전 대기 시간
minDuration Duration? null0ms (defaultMinDuration) 호버 상태 최소 유지 시간
hoverStyle CoreHoverStyle? null 효과 배율·그림자·전환 duration·타이밍 단일 진입점

타이밍 세 값(waitDuration / showDuration / minDuration)은 위젯 파라미터로도, hoverStyle 안으로도 넣을 수 있습니다 — hoverStyle 쪽이 뒤에 와서 이깁니다.

CoreHoverStyle 필드#

필드타입설명
scaleUpFactor double? Scale factor applied for the scaleUp effect override. null defers to [defaultScaleUpFactor].
scaleDownFactor double? Scale factor applied for the scaleDown effect override. null defers to [defaultScaleDownFactor].
fadeOpacity double? Opacity applied for the fade effect override. null defers to [defaultFadeOpacity].
brightenFactor double? Brightness multiplier applied for the brighten effect override. null defers to [defaultBrightenFactor].
shadowBoxShadow List<CoreShadowLayer>? Drop-shadow stack applied for the shadow effect override. null defers to [defaultShadowBoxShadow].
shadowBaseColor CoreColor? Base colour the shadow effect's layers are tinted with override. null defers to [defaultShadowBaseColor].
effectDuration Duration? Transition duration for the hover visual effect override. null defers to [defaultEffectDuration].
waitDuration Duration? Delay before the hover state is reported as active override. null defers to [defaultWaitDuration].
showDuration Duration? Delay before the hover state is reported as inactive after the pointer leaves override. null defers to [defaultShowDuration].
minDuration Duration? Minimum duration the hover state stays active once reported override. null defers to [defaultMinDuration].

Resolve chain#

CoreHoverStyle.defaultX (static const)
  → CoreHoverTheme.style                       // 프로젝트 공통
  → widget 타이밍 파라미터 (wait / show / min)   // 인스턴스별
  → widget.hoverStyle                          // 인스턴스별 (전체 chrome)

동작 스펙 (Behavior)#

  • 포인터가 진입하면 onEnter가 즉시 호출되고, 시각 효과가 즉시 적용됩니다.
  • 포인터가 waitDuration 동안 머무르면 onHover(true)가 발화합니다.
  • 포인터가 이탈하면 onExit가 즉시 호출되고, 시각 효과가 즉시 해제됩니다.
  • 이탈 후 showDuration 뒤에 onHover(false)가 발화합니다. 단 minDuration보다 빨리 이탈하면 남은 시간만큼 활성 상태가 유지됩니다.
  • scaleUp/scaleDown은 1.05 / 0.95 배율, fade는 0.8 불투명도, brighten은 1.1배 밝기, shadowCoreShadow.lg 그림자를 적용합니다.

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

✅ Do#

즉각적 시각 피드백은 effect, 디바운스된 상태 반응은 onHover

Hover(
  effect: CoreHoverEffect.shadow,
  onHover: (active) => setState(() => _isHighlighted = active),
  child: Card(
    cardStyle: const CoreCardStyle(boxShadow: CoreShadow.none),
    child: content,
  ),
)

effect는 포인터 진입 즉시 애니메이션되어 바로 반응하는 시각 피드백을 주고, onHover 콜백은 waitDuration(기본 500ms)이 지난 뒤에만 발화해 빠르게 지나가는 포인터로 상태가 흔들리는 것을 걸러줍니다.

CardboxShadow: CoreShadow.none 오버라이드는 effect: .shadow 전용 안전장치입니다 — 아래 "알려진 제약"의 표면 스타일 겹침 항목 참고.


❌ Don't#

키보드/터치 사용자도 같은 피드백을 받을 거라 가정하지 않기

// ❌ 포커스만으로 오는 사용자는 이 효과를 절대 보지 못함
Hover(
  effect: CoreHoverEffect.scaleUp,
  child: Button(onPressed: submit, child: const Text('Submit')),
)

Hover는 포인터 전용입니다 — 키보드 포커스나 터치로는 onHover/onEnter/onExit도, 시각 효과도 발생하지 않습니다. 포커스 시 동일한 피드백이 필요하면 별도로 구현해야 합니다.

접근성 (Accessibility)#

Hover 는 포인터 호버만 다루는 순수 래퍼입니다. 시맨틱을 아무것도 기여하지 않으므로, 리더가 읽는 내용은 전적으로 child 자신이 가진 것입니다.

역할 / Semantics#

없습니다. Flutter 는 효과 래퍼를 감싼 MouseRegion 만 반환하고 Semantics 를 붙이지 않으며, Web 은 rolearia-* 도 없는 <div> 를 렌더합니다(호출자가 직접 넘긴 attributes 만 통과합니다). 이 영역에 이름이나 역할이 필요하다면 child 쪽에서 붙여야 합니다.

키보드#

컴포넌트가 처리하는 키는 없습니다. Web 은 base 가 onKeyDown / onKeyUp 파라미터를 노출하지만 루트 <div>tabindex 가 없어, 포커스된 자손에서 버블링된 이벤트에만 발화하는 순수 passthrough 입니다. Flutter 에는 대응 파라미터 자체가 없습니다.

포커스#

없습니다. 루트는 포커스 대상이 아니고 포커스 링도 없습니다. 호버 상태는 오직 포인터에서만 옵니다 — Flutter 는 MouseRegion 의 enter/exit, Web 은 mouseenter / mouseleave 입니다.

스크린 리더#

child 가 말하는 그대로입니다. 다만 effect: CoreHoverEffect.shadow 에서 Flutter 는 그림자 레이어마다 child 를 복제하고, 그 복제본이 시맨틱 트리에 그대로 남습니다. 기본 2-레이어 그림자에서는 같은 내용이 3번 읽힙니다. Web 은 같은 그림자를 CSS filter 한 번으로 그리므로 1번만 읽힙니다.

알려진 제약#

  • 포인터 전용입니다. onHover / onEnter / onExit 는 키보드 포커스나 터치로는 발화하지 않고, 시각 효과도 적용되지 않습니다. 포커스에서 같은 반응이 필요하면 호출자가 직접 구현해야 합니다.
  • 동작 줄이기가 플랫폼별로 갈립니다. Flutter 는 effectDurationwaitDuration / showDuration / minDuration 을 모두 모션 해석 지점에 통과시켜, "동작 줄이기"가 켜지면 효과와 500ms 호버 지연이 함께 0 이 됩니다. Web 은 transition duration 을 inline 리터럴 ms 로 내보내고 타이밍도 감축하지 않아 prefers-reduced-motion 이 닿지 않습니다 — 전역 접근성 축 에 적힌 "토큰을 거치지 않는 임의 duration" 한계에 해당합니다.
  • .shadow 의 시맨틱 중복(위)은 아직 수정되지 않았습니다. 그 효과를 쓰면서 중복 announce 를 피해야 한다면 다른 효과를 쓰거나 child 를 직접 시맨틱 처리해야 합니다.
  • effect: .shadowchild 가 이미 그리고 있는 그림자와 겹쳐 캐스트됩니다. filter: drop-shadow()(Web) / 레이어마다 child 를 다시 칠하는 방식(Flutter) 모두 child 를 통째로 다시 그림자로 캐스트하므로, child 가 자기 자신의 box-shadow 를 이미 그리고 있으면(예: Card 의 rest elevation, 혹은 neo-brutalism 처럼 그림자 없는 표면에 스타일이 자체적으로 cast 를 얹는 surface style 아래) 두 그림자가 겹쳐 보이고 rest 상태보다 훨씬 커 보입니다. child 가 그 자체로 그림자를 그리지 않게 만드세요(위 예제의 boxShadow: CoreShadow.none 처럼) — 그림자가 없는 child(이미지, 아바타, plain box)로 바꿔도 됩니다.

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

항목FlutterWeb
클래스명HoverHover
효과 적용 AnimatedScale / AnimatedOpacity / ColorFiltered / AnimatedContainer CSS transform / opacity / filter / box-shadow + transition
이벤트 MouseRegion enter/exit mouseenter / mouseleave event
  • HoverCard: 호버 시 떠오르는 정보 패널
  • Tooltip: 호버 시 도움말 텍스트
  • Swap: 두 상태 간 전환 애니메이션