ScrollViewInterceptor | CoUI
본문으로 건너뛰기
LogoCoUI

ScrollViewInterceptor

데스크톱 트랙패드·휠 스크롤을 가로채 스크롤 뷰를 구동하는 래퍼 (인프라)

목차

마우스 가운데 버튼을 누른 채 포인터를 이동하면 스크롤을 구동하는 인프라 래퍼입니다. 누른 위치와 현재 위치의 차이로 자동 스크롤 이동량을 계산합니다. 일반 휠·트랙패드 스크롤을 대체하는 위젯은 아니며 enabled: false면 이 부가 제스처를 설치하지 않습니다.

Live Preview#

사용법#

ScrollViewInterceptor(
  child: ScrollArea(
    scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 160),
    child: Column(children: [/* 긴 콘텐츠 */]),
  ),
)

Props / Parameters#

W는 Flutter의 Widget, Web의 Component를 뜻하는 합성 슬롯 타입입니다.

파라미터타입기본값설명
childW필수자동 스크롤을 적용할 콘텐츠
enabled bool true false 면 가로채지 않고 네이티브 스크롤로 패스
scrollViewInterceptorStyle CoreScrollViewInterceptorStyle? null dragSpeed·maxScrollSpeed 등 동작 단일 진입점

스타일 시스템 (Style System)#

CoreScrollViewInterceptorStyle 필드#

필드타입설명
dragSpeed double? Multiplier applied to the pointer offset before the easing curve.
maxScrollSpeed double? Maximum absolute scroll velocity per frame (logical px).

동작 스펙 (Behavior)#

가운데 버튼을 누르면 시작 위치를 저장하고 포인터 이동 중 그 위치와의 거리를 읽습니다. 거리에 dragSpeed 배율과 세제곱 곡선을 적용하고 프레임 시간으로 나눈 뒤 축별 이동량을 maxScrollSpeed로 제한합니다. 최대 이동량은 한 tick의 논리 px, 시간 계산은 밀리초 단위입니다. 버튼을 놓으면 타이머를 멈춥니다. enabled: false면 자식의 기본 스크롤 경로를 그대로 사용하며 자체 키보드 동작·값 변경 콜백은 없습니다.

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

스크롤 뷰포트 자체가 필요하면 ScrollArea, 스크롤 위치에 따른 콘텐츠 빌드나 2축 이동이 필요하면 ScrollableClient를 사용합니다. ScrollViewInterceptor는 이미 존재하는 스크롤 경로에 가운데 버튼 제스처를 덧붙이는 역할입니다.

✅ Do#

가속하고 싶은 스크롤 영역 하나만 좁게 감싸기

ScrollViewInterceptor(
  child: ScrollArea(
    scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 240),
    child: content,
  ),
)

Web은 가장 가까운 스크롤 가능한 조상을 명령형으로 움직이고 Flutter는 포인터 hit-test 경로에 스크롤 이벤트를 전달합니다. 실제로 가속하려는 스크롤 영역 가까이에 배치하고 중첩 스크롤에서 어떤 영역이 반응하는지 확인하세요.


❌ Don't#

유일한 스크롤 수단으로 의존하지 않기

// ❌ 필수 동선인데 ScrollViewInterceptor 에만 의존
ScrollViewInterceptor(
  child: criticalScrollableContent,
)

마우스 가운데 버튼(Flutter PointerEvent.buttons != 4, Web MouseEvent.button != 1)에서만 동작합니다 — 터치·키보드·트랙패드 전용 사용자에게는 이 경로가 존재하지 않으므로, 감싼 스크롤 뷰 자체의 기본 스크롤이 항상 동작해야 하고 이 컴포넌트는 부가 가속 수단으로만 취급해야 합니다.

접근성 (Accessibility)#

역할#

양 플랫폼 모두 역할을 내보내지 않습니다. Flutter 는 Stacks 안에 Listener(그리고 자동 스크롤 중에만 커서 오버레이)를 두고 Semantics 를 만들지 않으며, Web 은 호출자가 넘긴 attributes 만 통과시키는 <div> 를 내보냅니다 — enabled: false 경로도 마찬가지로 빈 래퍼입니다. 시맨틱상 완전히 투명한 포인터 제스처 래퍼라, 감싼 콘텐츠의 시맨틱이 곧 전체 안내입니다.

키보드#

처리하는 키가 없습니다. Flutter 는 onPointerDown / onPointerMove / onPointerUp 만, Web 은 pointerdown / pointermove / pointerup / pointercancel 만 등록합니다 (Web 의 onKeyDown / onKeyUp 은 호출자 통과 슬롯). 자동 스크롤 제스처에 대응하는 키보드 경로는 양 플랫폼 모두 존재하지 않습니다.

포커스 관리#

포커스를 받지 않습니다. FocusNode / Focus / tabindex 가 없고 포커스 상태를 그리지도 않습니다. 이 컴포넌트가 렌더하는 유일한 상태는 자동 스크롤 중의 커서 변경입니다 (Flutter SystemMouseCursors.allScroll, Web cursor-all-scroll 클래스).

스크린 리더#

아무것도 안내하지 않습니다. 자동 스크롤을 쓸 수 있다는 사실, 세션이 시작됐다는 사실, 지금 스크롤 중이라는 사실 중 무엇도 알려지지 않습니다.

알려진 제약#

  • 마우스 가운데 버튼 전용입니다 — Flutter 는 PointerEvent.buttons != 4, Web 은 MouseEvent.button != 1 에서 곧바로 빠져나옵니다. 터치 · 키보드 · 트랙패드 전용 입력에는 대응 경로가 없으므로, 마우스 가운데 버튼을 쓸 수 없는 사용자에게는 이 기능이 존재하지 않는 것과 같습니다. 감싼 스크롤 뷰 자체의 스크롤은 그대로 동작하니, 이 래퍼는 부가 가속 수단으로만 취급하고 필수 동선에 두지 마세요.
  • Web 은 event.preventDefault() 로 브라우저 자체의 미들클릭 자동 스크롤을 대체합니다. 그 기본 동작에 의존하던 사용자는 이 구현의 감도/속도 토큰을 대신 받게 됩니다.
  • 스크롤은 가장 가까운 스크롤 가능한 조상을 명령형으로 이동시키는 방식이라 포커스가 따라가지 않고 어떤 안내도 발생하지 않습니다 — 화면은 움직였는데 리더의 위치는 그대로입니다.
  • enabled: false 면 양 플랫폼 모두 아무것도 하지 않는 통과 래퍼가 됩니다.

reduced motion 등 컴포넌트를 가로지르는 축은 전역 접근성 축에서 다룹니다.

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

항목FlutterWeb
가운데 버튼 판별 PointerEvent.buttons == 4 MouseEvent.button == 1
자동 이동Ticker의 경과 시간과 hit-test 대상 스크롤 이벤트주기 Timer와 조상 DOM 스크롤
종료 이벤트pointer uppointer up/cancel
비활성 래퍼 child를 직접 반환 passthrough 속성을 유지하는 <div>
브라우저 기본 자동 스크롤해당 없음가운데 버튼 기본 동작을 preventDefault로 대체