React + Rust + Wasm: Canvas Rendering

Neo Quest | Nikhil Gupta

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 using web_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. :)