Scroll Containers
Blinc provides scroll containers with WebKit-style momentum scrolling and bounce physics.
Basic Scroll
#![allow(unused)] fn main() { use blinc::layout::widgets::scroll::scroll; fn scrollable_content() -> impl ElementBuilder { scroll() .h(400.0) .child( div() .flex_col() .gap(8.0) .child(/* ... long content ... */) ) } }
Scroll Without Bounce
#![allow(unused)] fn main() { use blinc::layout::widgets::scroll::scroll_no_bounce; scroll_no_bounce() .h(400.0) .child(content) }
Scroll Configuration
#![allow(unused)] fn main() { use blinc::layout::widgets::scroll::{Scroll, ScrollConfig, ScrollDirection}; use blinc::animation::SpringConfig; Scroll::with_config(ScrollConfig { bounce_enabled: true, bounce_spring: SpringConfig::wobbly(), deceleration: 1500.0, velocity_threshold: 10.0, max_overscroll: 0.3, // 30% of viewport direction: ScrollDirection::Vertical, }) .h(400.0) .child(content) }
Configuration Presets
#![allow(unused)] fn main() { ScrollConfig::default() // Standard bounce ScrollConfig::no_bounce() // No bounce physics ScrollConfig::stiff_bounce() // Tight, minimal bounce ScrollConfig::gentle_bounce() // Soft, more bounce }
Scroll Directions
#![allow(unused)] fn main() { // Vertical only (default) Scroll::with_config(ScrollConfig { direction: ScrollDirection::Vertical, ..Default::default() }) // Horizontal only Scroll::with_config(ScrollConfig { direction: ScrollDirection::Horizontal, ..Default::default() }) // Both directions Scroll::with_config(ScrollConfig { direction: ScrollDirection::Both, ..Default::default() }) }
Scroll States
Scroll containers use ScrollState for physics-driven behavior:
#![allow(unused)] fn main() { ScrollState::Idle // Not scrolling ScrollState::Scrolling // User is dragging ScrollState::Decelerating // Momentum after release ScrollState::Bouncing // Edge bounce animation }
Example: Scrollable List
#![allow(unused)] fn main() { fn message_list() -> impl ElementBuilder { scroll() .h(500.0) .w_full() .child( div() .flex_col() .gap(8.0) .p(16.0) .child( (0..50).map(|i| { div() .p(12.0) .rounded(8.0) .bg(Color::rgba(0.15, 0.15, 0.2, 1.0)) .child( text(&format!("Message {}", i + 1)) .color(Color::WHITE) ) }) ) ) } }
Example: Horizontal Gallery
#![allow(unused)] fn main() { fn image_gallery() -> impl ElementBuilder { Scroll::with_config(ScrollConfig { direction: ScrollDirection::Horizontal, ..Default::default() }) .h(200.0) .w_full() .child( div() .flex_row() .gap(16.0) .p(16.0) .child( (0..10).map(|i| { div() .w(150.0) .h(150.0) .rounded(12.0) .bg(Color::rgba(0.2, 0.3, 0.5, 1.0)) .flex_center() .child(text(&format!("{}", i + 1)).size(24.0).color(Color::WHITE)) }) ) ) } }
Nested Scrolling
Scroll containers handle nested scrolling automatically. Inner scrolls consume events when they can scroll; outer scrolls take over at boundaries.
#![allow(unused)] fn main() { fn nested_scroll_example() -> impl ElementBuilder { // Outer vertical scroll scroll() .h(600.0) .child( div() .flex_col() .gap(16.0) .child(text("Section 1").size(24.0)) // Inner horizontal scroll .child( Scroll::with_config(ScrollConfig { direction: ScrollDirection::Horizontal, ..Default::default() }) .h(120.0) .child(horizontal_items()) ) .child(text("Section 2").size(24.0)) .child(more_content()) ) } }
Physics Parameters
| Parameter | Default | Description |
|---|---|---|
deceleration | 1500.0 | How quickly momentum decays (higher = faster stop) |
velocity_threshold | 10.0 | Minimum velocity to continue momentum |
max_overscroll | 0.3 | Maximum overscroll as fraction of viewport |
bounce_spring | wobbly | Spring config for bounce animation |
Programmatic Scroll Control
Blinc provides a powerful selector API for programmatic scroll control through ScrollRef. This allows you to scroll to specific elements, positions, or the top/bottom of content.
ScrollRef is one of several element handles — see
Element Refs for the shared contract they follow, and
for the handles onto fields and ordinary elements.
Creating a ScrollRef
Use ctx.use_scroll_ref() to create a persistent scroll reference:
#![allow(unused)] fn main() { use blinc::layout::selector::{ScrollRef, ScrollOptions, ScrollBehavior, ScrollBlock}; fn my_component(ctx: &WindowedContext) -> impl ElementBuilder { // Create a ScrollRef — auto-keyed by call site via #[track_caller], // so it survives UI rebuilds without a manual key. For loops or // factories called multiple times from one line, use // `ctx.use_scroll_ref_keyed(key)` instead. let scroll_ref = ctx.use_scroll_ref(); scroll() .bind(&scroll_ref) // Bind the ref to this scroll container .child(content) } }
Element IDs
Assign IDs to elements you want to scroll to:
#![allow(unused)] fn main() { fn card_list() -> impl ElementBuilder { div() .flex_col() .children( (0..10).map(|i| { div() .id(format!("card-{}", i)) // Assign unique ID .child(text(&format!("Card {}", i))) }) ) } }
Scrolling to Elements
Use scroll_to() or scroll_to_with_options() to scroll to an element by ID:
#![allow(unused)] fn main() { // Simple scroll to element scroll_ref.scroll_to("card-5"); // Scroll with options scroll_ref.scroll_to_with_options( "card-5", ScrollOptions { behavior: ScrollBehavior::Smooth, // Animate the scroll block: ScrollBlock::Center, // Center element in viewport ..Default::default() }, ); }
ScrollOptions
Configure how the scroll behaves:
#![allow(unused)] fn main() { ScrollOptions { behavior: ScrollBehavior::Smooth, // or ScrollBehavior::Auto (instant) block: ScrollBlock::Center, // Vertical alignment inline: ScrollInline::Nearest, // Horizontal alignment } }
| Block/Inline Value | Description |
|---|---|
Start | Align to top/left of viewport |
Center | Align to center of viewport |
End | Align to bottom/right of viewport |
Nearest | Scroll minimum distance to make visible (default) |
Other Scroll Operations
#![allow(unused)] fn main() { // Scroll to top/bottom scroll_ref.scroll_to_top(); scroll_ref.scroll_to_bottom(); // With smooth animation scroll_ref.scroll_to_bottom_with_behavior(ScrollBehavior::Smooth); // Scroll by relative amount scroll_ref.scroll_by(0.0, 100.0); // Scroll down 100px // Set absolute offset scroll_ref.set_scroll_offset(0.0, 500.0); }
Querying Scroll State
#![allow(unused)] fn main() { // Current offset let (x, y) = scroll_ref.offset(); let y = scroll_ref.scroll_y(); // Content and viewport sizes let content_size = scroll_ref.content_size(); let viewport_size = scroll_ref.viewport_size(); // Position checks if scroll_ref.is_at_top() { /* ... */ } if scroll_ref.is_at_bottom() { /* ... */ } // Scroll progress (0.0 = top, 1.0 = bottom) let progress = scroll_ref.scroll_progress(); }
Example: Carousel with Dot Navigation
Here's a complete example of a horizontal carousel with clickable navigation dots:
#![allow(unused)] fn main() { use blinc::app::prelude::*; use blinc::layout::selector::{ScrollBehavior, ScrollBlock, ScrollOptions, ScrollRef}; use blinc::layout::units::px; // Semantic unit for raw pixels fn carousel(ctx: &WindowedContext) -> impl ElementBuilder { let scroll_ref = ctx.use_scroll_ref(); let current_index = ctx.use_state_keyed("current_index", || 0usize); div() .flex_col() .items_center() .gap(16.0) // Horizontal scroll carousel .child( scroll() .bind(&scroll_ref) .direction(ScrollDirection::Horizontal) .w(400.0) .h(300.0) .child( div() .flex_row() .gap(20.0) .padding_x(px(60.0)) // Padding to center first/last cards .children( (0..5).map(|i| { div() .id(format!("card-{}", i)) // Element ID .w(280.0) .h(280.0) .bg(Color::rgba(0.2, 0.3, 0.5, 1.0)) .rounded(16.0) .child(text(&format!("Card {}", i + 1))) }) ), ), ) // Navigation dots .child(build_dots(ctx, &scroll_ref, ¤t_index)) } fn build_dots( ctx: &WindowedContext, scroll_ref: &ScrollRef, current_index: &State<usize>, ) -> impl ElementBuilder { div() .flex_row() .gap(12.0) .children( (0..5).map(|i| { let scroll_ref = scroll_ref.clone(); let current_index = current_index.clone(); div() .w(12.0) .h(12.0) .rounded(6.0) .bg(if i == current_index.get() { Color::rgba(0.4, 0.6, 1.0, 1.0) } else { Color::rgba(0.3, 0.3, 0.4, 1.0) }) .on_click(move |_| { current_index.set(i); scroll_ref.scroll_to_with_options( &format!("card-{}", i), ScrollOptions { behavior: ScrollBehavior::Smooth, block: ScrollBlock::Center, ..Default::default() }, ); }) }) ) } }
Best Practices
-
Set explicit height - Scroll containers need a bounded height to work.
-
Use overflow_clip on parent - Ensure parent clips overflowing content.
-
Prefer vertical for long content - Horizontal scrolling is less intuitive for lists.
-
Consider no-bounce for forms - Disable bounce for content that needs precise positioning.
-
Test nested scrolling - Verify inner/outer scroll interactions work as expected.
-
Use meaningful element IDs - Choose descriptive IDs like
"message-123"or"section-intro"for elements you need to scroll to. -
Prefer
ctx.use_scroll_ref()- Always use the context method rather thanScrollRef::new()for proper reactive integration.