DEV Community

bright inventions
bright inventions

Posted on • Edited on • Originally published at brightinventions.pl

Using WebAssembly with React

WebAssembly (WASM) is a binary format for the executable code in the browsers.
In this article, we will create a simple web application using React library, write and compile to WASM a part of our JavaScript code and after that link it to the application.

We need a minimal application with a React library. I don't describe how to create it from scratch, you can read about it in the article The minimal React + Webpack 4 + Babel Setup. The application in this repository is enough for our needs.

Preparing

To start using the minimal React application we can clone the repository:

$ git clone git@github.com:rwieruch/minimal-react-webpack-babel-setup.git wasm_react 
Enter fullscreen mode Exit fullscreen mode

Now we can install all dependencies and start the server:

$ cd wasm_react
$ yarn install
$ yarn start
Enter fullscreen mode Exit fullscreen mode

After that you can go to http://localhost:8080 and check if it works.

Create canvas component

The next thing we should do is to create a new React component with canvas and add the function to drawing.

For our new component we can create the new file:

$ touch src/canvas.js
Enter fullscreen mode Exit fullscreen mode

And put in it this code:

// src/canvas.js
import React, {Component} from "react";

class Canvas extends Component {

  componentDidMount() {
    let canvas = this.refs.canvas.getContext('2d');
    canvas.fillRect(0, 0, 100, 100);
  }

  render() {
    return (
        <canvas ref="canvas" width={this.props.width} height={this.props.height}/>
    )
  }
}

export default Canvas;
Enter fullscreen mode Exit fullscreen mode

This component creates canvas using parameters from props and after that you should see a black rectangle in canvas.

For rendering the new component we can add it to src/index.js:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

import Canvas from './canvas';

const title = 'My Minimal React Webpack Babel Setup';

ReactDOM.render(
  <Canvas height={500} width={500} />,
  document.getElementById('app')
);

module.hot.accept();
Enter fullscreen mode Exit fullscreen mode

Now you can go to a browser and check if you can see a black rectangle:

Black rectangle on canvas

Drawing fractals

The next thing what we will draw is incredibly beautiful Mandelbrot sets. First, we will implement it using JavaScript and after that we will reimplement it in WebAssembly. More theoretical background about that you can find in this article. I have just got the main function from this article.

Now we can add the mandelIter function to our Canvas component:

// scr/canvas.js
class Canvas extends Component {

//.....

mandelIter(x, y, maxIter) {
  let r = x;
  let i = y;
  for (let a = 0; a < maxIter; a++) {
    let tmpr = r * r - i * i + x;
    let tmpi = 2 * r * i + y;

    r = tmpr;
    i = tmpi;

    if (r * i > 5) {
      return a/maxIter * 100;
    }
  }

  return 0;
}

//.....
Enter fullscreen mode Exit fullscreen mode

After that, we can add to componentDidMount two loops to iterate over the all pixels in the canvas.

The updated function:

// src/canvas.js
componentDidMount() {
  let canvas = this.refs.canvas.getContext('2d');
  let mag = 200;
  let panX = 2;
  let panY = 1.25;
  let maxIter = 100;

  for (let x = 10; x < this.props.height; x++)  {
    for (let y = 10; y < this.props.width; y++)  {
      let m = this.mandelIter(x/mag - panX, y/mag - panY, maxIter);
      canvas.fillStyle = (m === 0) ? '#000' : 'hsl(0, 100%, ' + m + '%)'; 
      canvas.fillRect(x, y, 1,1);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

After this change you can see the Mandelbrot set on the page:

Mandelbrot set fractal draws on the canvas

It looks great, doesn't it?

Implementing in WebAssembly

Now we can implement a function mandelIter in WebAssembly. We can do it by using C++, Rust or Go. But here we will use C++ and an online compiler WebAssembly Explorer:

The function mandelIter implemented in C++:

float mandelIter(float x, float y, int maxIter) {
  float r = x;
  float i = y;
  for (int a = 0; a < maxIter; a++) {
    float tmpr = r * r - i * i + x;
    float tmpi = 2 * r * i + y;

    r = tmpr;
    i = tmpi;

    if (r * i > 5) {
      return a/(float) maxIter * 100;
    }
  }

  return 0;
}
Enter fullscreen mode Exit fullscreen mode

Our function after the compilation has some strange name: _Z10mandelIterffi. We will use this name in our JavaScript code.

WebAssembly Explorer in browser

After compiling, we can download and put the file in src folder. I have named it fractal.wasm.

For using wasm in React you just need to add import to Canvas-component:

// src/canvas.js
import React, {Component} from "react";

const wasm = import("./fractal.wasm");

class Canvas extends Component {
Enter fullscreen mode Exit fullscreen mode

The next step is updating the componentDidMount function:

// src/canvas.js

componentDidMount() {
  wasm.then(wasm => {
    const mandelIterWASM = wasm._Z10mandelIterffi;
    let canvas = this.refs.canvas.getContext('2d');
    let mag = 200;
    let panX = 2;
    let panY = 1.25;
    let maxIter = 100;

    for (let x = 10; x < this.props.height; x++)  {
      for (let y = 10; y < this.props.width; y++)  {
        // let m = this.mandelIter(x/mag - panX, y/mag - panY, maxIter);
        let m = mandelIterWASM(x/mag - panX, y/mag - panY, maxIter);
        canvas.fillStyle = (m === 0) ? '#000' : 'hsl(0, 100%, ' + m + '%)'; 
        canvas.fillRect(x, y, 1,1);
      }
    }
  });
}

Enter fullscreen mode Exit fullscreen mode

Now for drawing on canvas we are using the function implemented in WebAssembly.

You can manipulate variables mag, panX and panY to create another form of fractals:

All code you can find in my repository.

Originally published at brightinventions.pl

By Ivan Menshykov, Software Developer at Bright Inventions

Twitter

Top comments (5)

Collapse
 
dijky profile image
Lukas S. • Edited

I recommend declaring C++ functions you use in WebAssembly as extern "C" (unless you use C++ symbol naming features such as overloading, classes, or namespaces).

This way, the compiler won't mangle the name into a garbled mess:

extern "C" float mandelIter(float x, float y, int maxIter) { ...

Use as

wasm.mandelIter(x, y, maxIter);
Collapse
 
wordythebyrd profile image
Andrew Byrd 🐦

Any performance benefit using WebAssembly here rather than the JS implementation?

Collapse
 
aseem2625 profile image
Aseem Gupta

I have 2 canvas(one JS and one WASM) side by side. On calculating time differences using performance.now() for both, WASM turns out to be taking more time than JS fn. Not sure why.

Collapse
 
adrianmcli profile image
Adrian Li

You need to add the following to .babelrc

"plugins": ["@babel/plugin-syntax-dynamic-import"]

And also:

npm install @babel/plugin-syntax-dynamic-import
Collapse
 
askthepunkuzz profile image
Punkuzz

What is the advantage of web assembly?