Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Tick scroll physicstree.tick_scroll_physics(now_ms) advances active scroll deceleration and bounce springs
  2. Detect rebuild triggers — polls tree.needs_rebuild(), take_needs_rebuild(), and the reactive dirty flag
  3. Drain pending Stateful updates — applies queued render-prop and subtree changes from State::set / State::update
  4. Rebuild or incrementally updatetree.incremental_update(&element) for normal frames, full rebuild on resize
  5. Rendersurface.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 EventRouter desktop uses
  • Drag gestures — DRAG / DRAG_END events with deltas, same Stateful machinery
  • Reactive stateBlincContextState, State::set, Stateful::on_state all work unchanged
  • Animations — spring physics, keyframes, motion containers all tick from requestAnimationFrame
  • Asset fetchWebAssetLoader::fetch_bytes for runtime asset loading instead of bundling
  • Async setupWebApp::run_with_async_setup for fonts, CSS, or anything else that needs to .await before the first frame

Browser support

BrowserStatusNotes
Chrome / Chromium ≥ 113SupportedWebGPU enabled by default
Edge ≥ 113SupportedSame Chromium engine
Safari Technology Preview (pre Tahoe)Partial (flagged)Develop → Feature Flags → WebGPU . Pre-sequoia does not support Vertex Storage
Safari stable (Tahoe)SupportedWebGPU enabled by default
Firefox (≥ 141 on windows, ≥ 145 on MacOs)SupportedWebGPU 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)

FeatureStatusNotes
Mouse + wheel + keyboard inputRoutes through EventRouter
Drag gesturesDRAG / DRAG_END events with deltas
Touch inputPendingDOM touch* events need conversion to InputEvent::Touch
IME compositionPendingcompositionstart / update / endEventRouter::on_text
File dialogsPendingrfd doesn’t compile on wasm32; needs <input type="file"> bridge
System tray / notifications / global hotkeysWon’t fixBrowser sandbox doesn’t expose these
localStorage window-state persistencePendingTrivial follow-up using web-sys::Storage
Service worker / offline assetsOut of scopeApp-level concern
Multi-canvas / multi-viewPendingArchitecture supports it; no public API yet
A11y (ARIA roles, screen reader)PendingLarger architecture discussion — needs DOM mirror or accesskit-html

Next steps

  • Setup & Build — Cargo.toml, wasm-pack, the index.html HTML+JS scaffold
  • Examples — walkthrough of the four runnable web examples
  • Fonts & Assets — bundled vs fetched fonts, the WebAssetLoader API