-
-
Notifications
You must be signed in to change notification settings - Fork 10
Performance Tips
Blinc is designed for high performance, but following these guidelines ensures your UI stays smooth.
Do: Use stateful::<S>() for hover, press, and focus effects:
use blinc_layout::stateful::stateful;
fn hover_button() -> impl ElementBuilder {
stateful::<ButtonState>()
.px(16.0)
.py(8.0)
.rounded(8.0)
.on_state(|ctx| {
let bg = match ctx.state() {
ButtonState::Idle => Color::RED,
ButtonState::Hovered => Color::BLUE,
_ => Color::RED,
};
div().bg(bg)
})
.child(text("Hover me").color(Color::WHITE))
}Don't: Use if-else or signals for visual-only state changes:
// AVOID - causes full tree rebuild on every hover
let is_hovered = ctx.use_signal(false);
div()
.on_hover_enter(move |_| ctx.set(is_hovered, true))
.on_hover_leave(move |_| ctx.set(is_hovered, false))
.bg(if ctx.get(is_hovered).unwrap_or(false) {
Color::BLUE
} else {
Color::RED
})The stateful::<S>() pattern only updates the affected element, while signals with if-else rebuild the entire UI tree.
Signals trigger UI rebuilds. Batch related updates:
// Good - single rebuild
ctx.batch(|g| {
g.set(x, 10);
g.set(y, 20);
g.set(z, 30);
});
// Avoid - three rebuilds
ctx.set(x, 10);
ctx.set(y, 20);
ctx.set(z, 30);Keyed state persists across rebuilds. Use it for:
- Form input values
- Toggle states
- Selected items
Don't overuse - each key adds memory overhead.
For large lists, consider:
- Virtualization - Only render visible items
- Stable keys - Use consistent identifiers for list items
- Memoization - Cache expensive computations
// For very long lists, wrap in scroll and limit rendered items
scroll()
.h(500.0)
.child(
div()
.flex_col()
.child(
visible_items.iter().map(|item| render_item(item))
)
)For custom drawing:
- Minimize state reads - Read animated values once, not per-shape
- Use transforms - Push/pop transforms instead of recalculating positions
- Batch similar draws - Group shapes by color/brush
canvas(move |ctx, bounds| {
// Read once
let angle = timeline.lock().unwrap().get(entry_id).unwrap_or(0.0);
// Use transform for rotation
ctx.push_transform(Transform::rotate(angle));
// ... draw ...
ctx.pop_transform();
})- Use appropriate spring stiffness - Stiffer springs settle faster
- Limit simultaneous animations - Too many can cause jank
- Use timelines for loops - More efficient than many spring values
// Good - single timeline with multiple entries
let timeline = ctx.use_animated_timeline();
let (x, y, scale) = timeline.lock().unwrap().configure(|t| {
(t.add(0, 1000, 0.0, 100.0),
t.add(0, 1000, 0.0, 100.0),
t.add(0, 500, 1.0, 1.5))
});-
Clone Arc, not data - Use
Arc::clone()for shared state - Drop unused state - Clean up keyed state when no longer needed
- Avoid closures capturing large data - Clone only what's needed
// Good - clone the Arc, not the data
let data = Arc::clone(&shared_data);
// Avoid - captures entire struct
let large_struct = expensive_struct.clone();
div().on_click(move |_| use_struct(&large_struct))For applications with many images (galleries, feeds, chat), use lazy loading to defer loading until images are visible:
// Images in a scrollable gallery
scroll()
.h(600.0)
.child(
div()
.flex_row()
.flex_wrap()
.gap(8.0)
.child(
image_urls.iter().map(|url| {
img(*url)
.lazy() // Only loads when scrolled into view
.placeholder_color(Color::rgba(0.2, 0.2, 0.2, 1.0))
.w(150.0)
.h(150.0)
.cover()
})
)
)Benefits:
- Reduced initial memory - Only visible images are loaded
- Faster startup - No waiting for off-screen images
- Automatic cleanup - LRU cache evicts old images
Emoji images (emoji() and emoji_sized()) are automatically lazy-loaded. The ~180MB system emoji font is only loaded when emoji characters actually appear on screen.
Enable tracing to identify bottlenecks:
tracing_subscriber::fmt()
.with_env_filter("blinc_layout=debug")
.init();Look for:
- Frequent tree rebuilds
- Long frame times
- Excessive state updates
| Do | Don't |
|---|---|
Use Stateful for hover/press |
Use signals for visual-only changes |
| Batch signal updates | Update signals one at a time |
Use Arc::clone()
|
Clone large data into closures |
| Use timelines for loops | Create many spring values |
| Read animated values once | Read repeatedly in draw loops |
Getting Started
Mobile Development
Core Concepts
Animation
Components
Component Library (blinc_cn)
Widgets
Advanced
Architecture