React + Rust + Wasm: Canvas Rendering

Neo Quest | Nikhil Gupta / October 26, 2022
3 min read
Summary
In this article, we will render a circle in our React application from our Rust WASM library using 2D Canvas APIs. We will build on the previous tutorial available here.
Add Canvas Features to web-sys
First of all, let's enable the required features for web-sys
dependency:
# Cargo.toml
[package]
name = "rust-wasm-lib"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.83"
[dependencies.web-sys]
version = "0.3.4"
features = [
'Document',
'Element',
'HtmlElement',
'Node',
'Window',
'CanvasRenderingContext2d',
'HtmlCanvasElement'
]
Canvas Rendering using Rust
Last time, we exposed a function to add two numbers in Rust and display the result on screen. Now, let's modify our lib.rs
to expose another function that will take the DOM Id of the canvas element, extract a 2D context and render a circle.
#[wasm_bindgen]
pub fn render(element_id: &str) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let canvas = document
.get_element_by_id(element_id)
.expect("no canvas found");
let canvas: web_sys::HtmlCanvasElement = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
let context = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
context.begin_path();
context
.arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0)
.unwrap();
context.stroke();
}
Let's go through the code step-by-step.
- Expose a function called
render
that takes the ID of Canvas element already added to the DOM - Get the global
window
usingweb_sys
API - Get the document from this window
- Get the canvas element from this document for the given
element_id
- Cast it to
web_sys::HtmlCanvasElement
that exposes a way to acquire 2D context - Get the rendering context
- Begin a path
- Draw an arc with from 0 to 2pi essentially rendering a circle
- Stroke the resultant path so that it is visible on the screen
Build the new wasm library
Let's run wasm-pack again to build the updated library
wasm-pack build --target web
Call the new function from the demo app
Finally, let's add a canvas to our DOM and call the exported render
function from our App.ts
file like so:
// App.ts
import React, { useEffect } from 'react';
import init, { render } from "rust-wasm-lib";
import './App.css';
function App() {
useEffect(() => {
init().then(() => {
render("canvas");
});
}, [])
return (
<div className="App">
<canvas id="canvas" width={400} height={400} />
</div>
);
}
export default App;
Now, if you run the updated app, you should see a circle on the screen. :)