ScrollViewInterceptor#
데스크톱 포인터(트랙패드·마우스 휠) 스크롤을 가로채 child 스크롤 뷰를 부드럽게 구동하는 인프라 래퍼입니다. 스크롤 가능한 영역을 감싸 데스크톱 스크롤 감도/속도를 디자인 시스템 토큰으로 통일합니다.
enabled 가 false 면 가로채지 않고 네이티브 스크롤로 패스합니다.
Live Preview#
class ScrollViewInterceptorDefaultExample extends StatelessComponent {
const ScrollViewInterceptorDefaultExample({super.key});
@override
Component build(BuildContext context) {
return div(
styles: Styles(raw: {'width': '260px'}),
[
ScrollViewInterceptor(
child: ScrollArea(
scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 160),
child: div(
classes: 'flex flex-col gap-${CoreSpace.scale.space8}',
[
for (var i = 1; i <= 20; i += 1) Text('항목 $i').bodyMedium,
],
),
),
),
],
);
}
}
class ScrollViewInterceptorDefaultExample extends StatelessWidget {
const ScrollViewInterceptorDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 260,
child: ScrollViewInterceptor(
child: ScrollArea(
scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: CoreSpace.space8,
children: [
for (var i = 1; i <= 20; i += 1) Text('항목 $i').bodyMedium,
],
),
),
),
);
}
}
class ScrollViewInterceptorChainExample extends StatelessComponent {
const ScrollViewInterceptorChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
styles: Styles(raw: {'width': '260px'}),
[
ScrollViewInterceptor(
child: ScrollArea(
scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 160),
child: div(
classes: 'flex flex-col gap-${CoreSpace.scale.space8}',
[
for (var i = 1; i <= 20; i += 1) Text('항목 $i').bodyMedium,
],
),
),
).withStyle(
const CoreScrollViewInterceptorStyle(
dragSpeed: 0.05,
maxScrollSpeed: 24,
),
),
],
);
}
}
class ScrollViewInterceptorChainExample extends StatelessWidget {
const ScrollViewInterceptorChainExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 260,
child:
ScrollViewInterceptor(
child: ScrollArea(
scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: CoreSpace.space8,
children: [
for (var i = 1; i <= 20; i += 1) Text('항목 $i').bodyMedium,
],
),
),
).withStyle(
const CoreScrollViewInterceptorStyle(
dragSpeed: 0.05,
maxScrollSpeed: 24,
),
),
);
}
}
사용법#
ScrollViewInterceptor(
child: ScrollArea(
scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 160),
child: Column(children: [/* 긴 콘텐츠 */]),
),
)
Props#
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child |
Widget / Component |
required | 스크롤 가로채기를 적용할 스크롤 뷰 |
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). |
사용 가이드라인 (Usage Guidelines)#
✅ Do#
가속하고 싶은 스크롤 영역 하나만 좁게 감싸기
ScrollViewInterceptor(
child: ScrollArea(
scrollAreaStyle: const CoreScrollAreaStyle(maxHeight: 240),
child: content,
),
)
"가장 가까운 스크롤 가능한 조상"을 명령형으로 움직이는 방식이라, 실제로 가속하려는 스크롤 뷰 하나만 감싸야 어떤 스크롤 영역이 반응할지 모호해지지 않습니다.
❌ Don't#
유일한 스크롤 수단으로 의존하지 않기
// ❌ 필수 동선인데 ScrollViewInterceptor 에만 의존
ScrollViewInterceptor(
child: criticalScrollableContent,
)
마우스 가운데 버튼(Flutter PointerEvent.buttons != 4, Web MouseEvent.button != 1)에서만 동작합니다 — 터치·키보드·트랙패드 전용 사용자에게는 이 경로가 존재하지 않으므로, 감싼 스크롤 뷰 자체의 기본 스크롤이 항상 동작해야 하고 이 컴포넌트는 부가 가속 수단으로만 취급해야 합니다.
접근성 (Accessibility)#
역할 (Semantics)#
양 플랫폼 모두 역할을 내보내지 않습니다. 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 등 컴포넌트를 가로지르는 축은 전역 접근성 축에서 다룹니다.