Web Development
Blinc compiles to wasm32-unknown-unknown and runs inside a <canvas> element via wgpu’s WebGPU backend (with WebGL2 fallback). The same Rust UI code that runs on desktop and mobile runs in the browser, with no source-level changes.
The web target is Tier 2 / preview: it ships, has runnable examples, and exercises the same render / event / state pipelines as the native runners — but a few platform-specific bits (touch input, IME, file dialogs, multi-canvas, accessibility) are deliberately out of scope for the initial cut.
Cross-platform architecture
┌─────────────────────────────────────────────────────────────┐
│ Your Blinc App │
│ (Shared Rust UI code, state, animations) │
└─────────────────────────────┬───────────────────────────────┘
│
┌──────┬───────────────┼───────────────┬──────────┐
│ │ │ │ │
┌───▼──┐ ┌─▼──────┐ ┌─────▼──────┐ ┌─────▼─────┐ ┌──▼──┐
│macOS │ │Windows │ │ Android │ │ iOS │ │ Web │
│ Metal│ │DX12/Vk │ │ Vulkan │ │ Metal │ │WebGPU│
└──────┘ └────────┘ └────────────┘ └───────────┘ └─────┘
The web runner (crates/blinc_app/src/web.rs) is a sibling of the desktop / Android / iOS runners. It owns the same 5-phase frame loop:
- Tick scroll physics —
tree.tick_scroll_physics(now_ms)advances active scroll deceleration and bounce springs - Detect rebuild triggers — polls
tree.needs_rebuild(),take_needs_rebuild(), and the reactive dirty flag - Drain pending Stateful updates — applies queued render-prop and subtree changes from
State::set/State::update - Rebuild or incrementally update —
tree.incremental_update(&element)for normal frames, full rebuild on resize - Render —
surface.get_current_texture()→BlincApp::render_tree(...)→frame.present()
The driver is a requestAnimationFrame chain that fires every browser frame.
Key features
- GPU rendering via WebGPU — same SDF / batching / glass / 3D pipelines as desktop
- Mouse + wheel + keyboard input — routed through the same
EventRouterdesktop uses - Drag gestures — DRAG / DRAG_END events with deltas, same
Statefulmachinery - Reactive state —
BlincContextState,State::set,Stateful::on_stateall work unchanged - Animations — spring physics, keyframes, motion containers all tick from
requestAnimationFrame - Asset fetch —
WebAssetLoader::fetch_bytesfor runtime asset loading instead of bundling - Async setup —
WebApp::run_with_async_setupfor fonts, CSS, or anything else that needs to.awaitbefore the first frame
Browser support
| Browser | Status | Notes |
|---|---|---|
| Chrome / Chromium ≥ 113 | Supported | WebGPU enabled by default |
| Edge ≥ 113 | Supported | Same Chromium engine |
| Safari Technology Preview (pre Tahoe) | Partial (flagged) | Develop → Feature Flags → WebGPU . Pre-sequoia does not support Vertex Storage |
| Safari stable (Tahoe) | Supported | WebGPU enabled by default |
| Firefox (≥ 141 on windows, ≥ 145 on MacOs) | Supported | WebGPU enabled by default |
The runtime falls back to WebGL2 where WebGPU is unavailable, but storage-buffer-dependent pipelines need WebGPU — specifically the SDF aux buffer and any future compute shaders. Plain rendering, text, and SVG work on WebGL2; advanced 3D and particles do not.
Project structure
A typical Blinc web project looks like this:
my-web-app/
├── Cargo.toml # crate-type = ["cdylib", "rlib"]
├── src/
│ └── lib.rs # #[wasm_bindgen(start)] + build_ui
├── fonts/ # Optional: fonts to bundle or fetch
│ └── Inter.ttf
├── index.html # canvas + WebGPU/WebGL2 probe + ES module loader
├── pkg/ # Generated by `wasm-pack build` (gitignored)
│ ├── my_web_app.js
│ └── my_web_app_bg.wasm
└── serve.sh # Static file server (python3 / ruby / npx)
The runnable examples/web_hello, examples/web_scroll, examples/web_drag, and examples/web_assets follow this exact layout — copy any of them as a starting point.
What’s deliberately different from desktop
scroll() defaults to bounce-disabled
The native scroll() widget uses spring-bounce at edges. On wasm32, the default is flipped: scroll() returns a no-bounce config. DOM wheel events have no reliable “gesture ended” phase, and macOS layers ~800ms of OS-level momentum-scroll events on top of the user’s gesture — every workaround for “when did the user finish?” produces either a perceptible bounce lag or a wobble as the spring restarts each time the OS momentum re-overscrolls. Native HTML scrolling has no rubber-band either, except at the page level in iOS / macOS Safari, and that’s owned by the OS, not by anything inside a <canvas>.
Apps that explicitly want bounce on web can opt in via Scroll::with_config(ScrollConfig::default()) or supply their own SharedScrollPhysics to Scroll::with_physics.
Async clipboard
web_sys::Clipboard::write_text / read_text are async-only. Text-editor widgets’ Cmd+C / Cmd+V keybinds still trigger on the keypress, but the clipboard write is fire-and-forget and the read can’t be await-ed inside a synchronous handler.
Single canvas
WebApp::run takes a single canvas ID. Multi-canvas / multi-view setups (e.g. an in-page editor preview alongside the main app) are architecturally supported via the shared ElementRegistry, but no WebApp::run_multi API has shipped yet.
What’s missing (Tier 2 gaps)
| Feature | Status | Notes |
|---|---|---|
| Mouse + wheel + keyboard input | ✅ | Routes through EventRouter |
| Drag gestures | ✅ | DRAG / DRAG_END events with deltas |
| Touch input | Pending | DOM touch* events need conversion to InputEvent::Touch |
| IME composition | Pending | compositionstart / update / end → EventRouter::on_text |
| File dialogs | Pending | rfd doesn’t compile on wasm32; needs <input type="file"> bridge |
| System tray / notifications / global hotkeys | Won’t fix | Browser sandbox doesn’t expose these |
localStorage window-state persistence | Pending | Trivial follow-up using web-sys::Storage |
| Service worker / offline assets | Out of scope | App-level concern |
| Multi-canvas / multi-view | Pending | Architecture supports it; no public API yet |
| A11y (ARIA roles, screen reader) | Pending | Larger architecture discussion — needs DOM mirror or accesskit-html |
Next steps
- Setup & Build — Cargo.toml,
wasm-pack, theindex.htmlHTML+JS scaffold - Examples — walkthrough of the four runnable web examples
- Fonts & Assets — bundled vs fetched fonts, the
WebAssetLoaderAPI