ScrollableClient#
고정 크기 뷰포트 안에서 2D 스크롤을 제공하고, 매 스크롤 프레임마다 현재
offset / viewportSize를 builder 콜백에 전달하는 컴포넌트입니다.
CoTable의 가로/세로 고정 셀이나 가상화 레이아웃처럼 스크롤 위치에 따라
렌더링을 조정해야 하는 서브트리에 최적화되어 있습니다.
Live Preview#
Web
Offset: 0.0, 0.0
Viewport: 0 × 0
Drag or scroll to move around this 1200 × 560 content.
Flutter
Loading Flutter...
class ScrollableClientDefaultExample extends StatelessComponent {
const ScrollableClientDefaultExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
final ts = context.theme.typography;
return div(
[
CoScrollableClient(
diagonalDragBehavior: CoreDiagonalDragBehavior.free,
builder: (context, offset, viewportSize, child) {
return div(
[
Component.text(
'Offset: ${offset.dx.toStringAsFixed(1)}, '
'${offset.dy.toStringAsFixed(1)}',
),
CoGap(size: CoreSpace.space8),
Component.text(
'Viewport: ${viewportSize.width.toStringAsFixed(0)} × '
'${viewportSize.height.toStringAsFixed(0)}',
),
CoGap(size: CoreSpace.space16),
Component.text(
'Drag or scroll to move around this 1200 × 560 content.',
),
],
classes:
'flex flex-col bg-${cs.surfaceContainer} text-${ts.bodyMedium} text-${cs.onSurface} rounded-${CoreRadius.scale.radius16} p-${CoreSpace.scale.space16}',
styles: Styles(
raw: const {
'width': '${1200 / 16}rem',
'height': '${560 / 16}rem',
},
),
);
},
),
],
styles: Styles(
raw: const {
'height': '${320 / 16}rem',
'width': '100%',
'min-width': '0',
},
),
);
}
}
class ScrollableClientDefaultExample extends StatelessWidget {
const ScrollableClientDefaultExample({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SizedBox(
height: 320,
child: CoScrollableClient(
diagonalDragBehavior: CoreDiagonalDragBehavior.free,
builder: (context, offset, viewportSize, child) {
return Container(
width: 1200,
height: 560,
padding: const EdgeInsets.all(CoreSpace.space16),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(CoreRadius.radius16),
),
child: DefaultTextStyle.merge(
style: theme.typography.bodyMedium.copyWith(
color: theme.colorScheme.onSurface,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Offset: ${offset.dx.toStringAsFixed(1)}, '
'${offset.dy.toStringAsFixed(1)}',
),
const CoGap(size: CoreSpace.space8),
Text(
'Viewport: ${viewportSize.width.toStringAsFixed(0)} × '
'${viewportSize.height.toStringAsFixed(0)}',
),
const CoGap(size: CoreSpace.space16),
const Text(
'Drag or scroll to move around this 1200 × 560 content.',
),
],
),
),
);
},
),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 가로/세로 동시 스크롤이 필요한 고정 크기 영역을 만들 때
- 현재 스크롤 오프셋을 기준으로 자식을 다르게 렌더링해야 할 때 (예: 테이블의 frozen 헤더/컬럼, 가상화된 리스트)
- Web/Flutter 양쪽에 동일한 builder 인터페이스로 스크롤 영역을 재사용하고 싶을 때
대신 다른 컴포넌트를 사용하세요:
-
SingleChildScrollView/<div overflow:auto>로 충분한 단방향 스크롤: 그냥 네이티브 스크롤 컨테이너를 쓰는 게 더 가볍습니다 - 리사이저블 레이아웃이 필요:
CoResizable
기본 사용법 (Basic Usage)#
SizedBox(
height: 320,
child: CoScrollableClient(
diagonalDragBehavior: CoreDiagonalDragBehavior.free,
builder: (context, offset, viewportSize, child) {
return CoCard(
cardStyle: const CoreCardStyle(
sizing: CoreCardSizing.fixed,
width: 720,
height: 560,
),
child: /* content */,
);
},
),
)
Web에서도 동일한 생성자로 사용합니다 — 단 builder의 offset은
({double dx, double dy}) 레코드, viewportSize는
({double width, double height}) 레코드입니다.
Props / Parameters#
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
builder |
CoreScrollableBuilder<W, Off, Sz> |
required | 매 스크롤 프레임마다 호출되는 빌더 (context, offset, viewportSize, child) |
child |
W? |
null |
builder에 그대로 전달되는 정적 자식 (리빌드 비용 절감용) |
mainAxis |
CoreAxis |
vertical |
주 스크롤 축 (diagonalDragBehavior 결정에도 사용) |
verticalDetails |
CoreScrollableDetails |
.vertical() |
세로 축의 direction + initialOffset |
horizontalDetails |
CoreScrollableDetails |
.horizontal() |
가로 축의 direction + initialOffset |
diagonalDragBehavior |
CoreDiagonalDragBehavior? |
theme or none |
대각선 드래그 처리 방식 |
overscroll |
bool? |
theme or false |
끝에서 계속 스크롤 허용 여부 (네이티브 rubber-band) |
clipBehavior |
Clip / CoreClip |
hardEdge |
오버플로우 클리핑 (Web: CoreClip.none=overflow:visible) |
hitTestBehavior |
CoreHitTestBehavior? |
theme or opaque |
포인터 이벤트 수신 방식 |
keyboardDismissBehavior |
CoreKeyboardDismissBehavior? |
theme or manual |
드래그 스크롤 시 포커스/키보드 해제 여부 |
Flutter 전용#
| 이름 | 타입 | 설명 |
|---|---|---|
flutterVerticalDetails |
ScrollableDetails? |
controller / physics / decorationClipBehavior 등 프레임워크 전용 파라미터 |
flutterHorizontalDetails |
ScrollableDetails? |
위와 동일 (가로 축) |
primary |
bool? |
PrimaryScrollController 사용 여부 |
dragStartBehavior |
DragStartBehavior? |
Flutter gesture arena 전용 |
접근성 (Accessibility)#
-
스크롤바는 네이티브 OS 스크롤바 + CoUI
onSurface틴트로 렌더링되어 사용자 설정(감추기/포커스 색)이 반영됩니다. -
keyboardDismissBehavior: CoreKeyboardDismissBehavior.onDrag로 설정하면 드래그 스크롤이 시작될 때document.activeElement(Web) / 현재 포커스된 FocusScope(Flutter)가 해제되어 모바일에서 소프트 키보드가 닫힙니다.
관련 컴포넌트#
- CoTable — ScrollableClient와 함께 가상화/frozen 셀 구현
- CoResizable — 사용자 지정 분할 패널