A shareable whiteboard with Canvas, Socket.io, and React.
Published: 2020-07-17 18:23:34

####Do you need a shareable Canvas for your React Project? 

The Socket.io documentation does a great job of getting you started with a whiteboard, but unfortunately, it is built-in HTML. In this blog, we see what it takes to create a whiteboard With React. Then make it shareable through a socket.io connection!

---

Start by using `create-react-app.` 

```
npx create-react-app my-whiteboard
cd my-whiteboard
```
---

Go ahead and bring in the npm packages you will need.

```
npm i socket.io
npm i socket.io-client
```
---

## Starting with the back end

Let us begin with the easy part. Creating our socket server and listening for a connection. In your `my-whiteboard` directory, you want to create a folder called `server` and a file named `index.js.`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/s8z1v7oapc4s8wj1377j.jpg)

---

`Create-react-app` already comes with `express` as an installed node package, so let us make use of it at the top of our `index.js.` The `var app = express()` statement creates a new express application for you.

We can transfer data over the HyperText Transfer Protocol (HTTP) with a Node.js built-in module called `HTTP.` So bring it in with `const HTTP = require('HTTP')`.

Next, we will essentially turn our computer into a server with `HTTP.createServer(app).` 

Here is where we are at so far:

``` Java
const express = require('express');
const app = express();

const http = require('http');
const server = http.createServer(app);
```
---

Now you should pass your created server to socket.io.

``` Java
const socket = require('socket.io');
const io = socket(server);
```
---
If you do not understand how socket works, the [socket.io documentation] (https://socket.io/demos/whiteboard/) sums it up quite nicely.

> Socket.io is a library that enables real-time, bidirectional, and event-based communication between the browser and the server.

We will essentially establish our server, and `socket.io` will be listening for a 'connection.' When the client-side `socket` emits a signal, our server-side will make that connection and execute any commands create. Usually, this will involve 'emitting' data back to the client. 

After we establish the connection, we want to listen for a drawing `event.` We can then take that data passed to use and broadcast it, attaching the 'drawing' label. 

``` Java
io.on('connection', onConnection);

function onConnection(socket){
  socket.on('drawing', (data) => socket.broadcast.emit('drawing', data));
}
```
---

Next, we can decide on a port to listen on. Make sure to call .listen on the server, not the app. 

Here is a look at our entire index.js:

``` Java
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const socket = require('socket.io');
const io = socket(server);

io.on('connection', onConnection);

function onConnection(socket){
  socket.on('drawing', (data) => socket.broadcast.emit('drawing', data));
}

const port = 8080;
server.listen(port, () => console.log(`server is running on port ${port}`));
```
---
## Creating our Canvas Application

Before we get to the front end code, let us talk about our file structure.

1. Make a `styles` folder inside of the `src` folder.
2. Create a `board.css` file inside of your new styles folder.
3. Create a `Board.js` file in the src folder

####Here is a look at my front end file structure:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kw2u85t8n15qan0fl4qo.jpg)

---

In this demo, we can take the app.js file out of the application and replace it with the board. Go into your `index.js` file and render your new Board Component.

``` Java
import Board from './Board.js';

ReactDOM.render(
  <React.StrictMode>
    <Board />
  </React.StrictMode>,
  document.getElementById('root')
);
```
---

I have included the full code for the board.js at the bottom of this blog. 

If you would like to hang around and read about what's inside, we can do that too!

### board.css

In our `board.css` can set our board `height` and `width` to 100%, then set the `position` to `absolute`. Then the whiteboard fills in the entirety of its container. 

We can then set our color classes, necessary because of how we will be changing colors on click. 

Here is a look at the board.css: 

``` Java
 * {
  box-sizing: border-box;
}

.whiteboard {
  height: 100%;
  width: 100%;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
}

.colors {
  position: fixed;
}

.color {
  display: inline-block;
  height: 48px;
  width: 48px;
}

.color.black { background-color: black; }
.color.red { background-color: red; }
.color.green { background-color: green; }
.color.blue { background-color: blue; }
.color.yellow { background-color: yellow; }
```
---

###Board.js

At the top of your Board.js, you should bring in your React tools, the board.css, and `io` from the `socket.io-client` package we installed earlier. 

``` Java
import React, { useRef, useEffect } from 'react';
import io from 'socket.io-client';
import './styles/board.css';
```
---

We can now `useRef` to grab our whiteboard, color, and socket elements, references we will create later when we render our HTML. If you are unfamiliar with useRef and would like to learn more, there is an excellent blog about it [here] (https://medium.com/@masonk000/understanding-useref-513b1bbe00dc)

``` Java
const Board = () => {
  const canvasRef = useRef(null);
  const colorsRef = useRef(null);
  const socketRef = useRef();
```
---
 
We will now begin our `useEffect` hook. It will make sure our code within runs when the component loads. 

``` Java
useEffect(() => {
```
---

Set the current property of our `canvisRef` element to the variable `canvas.` Then, by calling `getcontext('2d')` we will set the drawing context of our canvas to a 2d plane and thus giving us access to some unique `2d` methods built into the canvas. 

```Java
  useEffect(() => {
    const canvas = canvasRef.current;
    const context = canvas.getContext('2d');
```
---
####The Colors
Now add click listeners to your color palette. Set your starting color to **black**. Then create a color update function. Then loop through your color elements and execute `onColorUpdate` when clicked. 

```Java
    const colors = document.getElementsByClassName('color');

    const current = {
      color: 'black',
    };

    const onColorUpdate = (e) => {
      current.color = e.target.className.split(' ')[1];
    };

    for (let i = 0; i < colors.length; i++) {
      colors[i].addEventListener('click', onColorUpdate, false);
    }
    let drawing = false;
```
---
#### The Drawing
Create a drawLine function. The `beginPath()` and `moveTo()` methods move the path to the canvas's specified point. The `lineTo()` method will take in the coordinates of our ending point. Then `stroke()` will draw the path we have defined. Finally, we can use `closePath()` to bring us back to our starting point.

When creating draw lines, we will `emit` the line back to the socket server. The `emit` variable is set to a boolean because we only want to send the lines we draw, not the lines we have received. (we do not want to create an infinite loop)!

```Java
    const drawLine = (x0, y0, x1, y1, color, emit) => {
      context.beginPath();
      context.moveTo(x0, y0);
      context.lineTo(x1, y1);
      context.strokeStyle = color;
      context.lineWidth = 2;
      context.stroke();
      context.closePath();

      if (!emit) { return; }
      const w = canvas.width;
      const h = canvas.height;

      socketRef.current.emit('drawing', {
        x0: x0 / w,
        y0: y0 / h,
        x1: x1 / w,
        y1: y1 / h,
        color,
      });
    };
```
---
#### Mouse movement and clicks

The next three functions will check for mouse movement, a mouse click, or finger touch. They will utilize the `drawLine` function we just created. `onMouseDown` will check for a click, then set the `drawing` variable to `true` and begin our line. 

The `onMouseMove` function will check if we have begun our line. If so, It will grab our mouse's start position and track its path, sending the place to our `drawLine` function to be rendered and shared. 

Finally, when the user lets off their mouse or finger, the `drawing` variable will get to false, and a final small line is drawn and emitted. 

Without some throttle, `onMouseMove` would get called a ridiculous amount of times to create a `throttle` function in the following step.

```Java
    const onMouseDown = (e) => {
      drawing = true;
      current.x = e.clientX || e.touches[0].clientX;
      current.y = e.clientY || e.touches[0].clientY;
    };
    
    const onMouseMove = (e) => {
      if (!drawing) { return; }
      drawLine(current.x, current.y, e.clientX || e.touches[0].clientX, e.clientY || e.touches[0].clientY, current.color, true);
      current.x = e.clientX || e.touches[0].clientX;
      current.y = e.clientY || e.touches[0].clientY;
    };
    
    const onMouseUp = (e) => {
      if (!drawing) { return; }
      drawing = false;
      drawLine(current.x, current.y, e.clientX || e.touches[0].clientX, e.clientY || e.touches[0].clientY, current.color, true);
    };
```
---
#### The throttle
We do not want to overload our socket connection. A `throttle` can help us. Essentially this function will take a callback and a delay time (in milliseconds). It works by grabbing the current time and comparing it to the time `throttle` was called initially.  

```Java
    const throttle = (callback, delay) => {
      let previousCall = new Date().getTime();
      return function() {
        const time = new Date().getTime();

        if ((time - previousCall) >= delay) {
          previousCall = time;
          callback.apply(null, arguments);
        }
      };
    };
```
---

#### Setting the event listeners and canvas resizing
Now you can have the canvas listen for mouse clicks or touch. 

You also want to have the canvas listen for a screen resize and set the current canvas width and height to the changed window.

```Java
    const onResize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    };

    window.addEventListener('resize', onResize, false);
    onResize();
```
---
### Our client-side socket connection:
Now create the `onDrawingEvent` function that will fire when the client-side socket receives a 'drawing' signal from the server. When the drawing object is received, we want to send it to the drawLine function, omitting the last emit parameter. 

```Java
    const onDrawingEvent = (data) => {
      const w = canvas.width;
      const h = canvas.height;
      drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color);
    }

    socketRef.current = io.connect('/');
    socketRef.current.on('drawing', onDrawingEvent);
  }, []);
```
---

### The Canvas and Color elements
Return and render your canvas and color elements along with their references and classes. If you wanted to give the color blocks or the canvas a unique look you can pick a style library or modify the CSS
```Java
   return (
    <div>
      <canvas ref={canvasRef} className="whiteboard" />

      <div ref={colorsRef} className="colors">
        <div className="color black" />
        <div className="color red" />
        <div className="color green" />
        <div className="color blue" />
        <div className="color yellow" />
      </div>
    </div>
  );
}; 
```
---
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/k44qd38d3whs9uosnzzs.jpg)
---
### Here is the full board.jsx for an easy copy and paste
---
```Java
import React, { useRef, useEffect } from 'react';
import io from 'socket.io-client';
import './styles/board.css';


const Board = () => {
  const canvasRef = useRef(null);
  const colorsRef = useRef(null);
  const socketRef = useRef();

  useEffect(() => {

    // --------------- getContext() method returns a drawing context on the canvas-----

    const canvas = canvasRef.current;
    const test = colorsRef.current;
    const context = canvas.getContext('2d');

    // ----------------------- Colors --------------------------------------------------
   
    const colors = document.getElementsByClassName('color');
    console.log(colors, 'the colors');
    console.log(test);
    // set the current color
    const current = {
      color: 'black',
    };

    // helper that will update the current color
    const onColorUpdate = (e) => {
      current.color = e.target.className.split(' ')[1];
    };

    // loop through the color elements and add the click event listeners
    for (let i = 0; i < colors.length; i++) {
      colors[i].addEventListener('click', onColorUpdate, false);
    }
    let drawing = false;

    // ------------------------------- create the drawing ----------------------------
    
    const drawLine = (x0, y0, x1, y1, color, emit) => {
      context.beginPath();
      context.moveTo(x0, y0);
      context.lineTo(x1, y1);
      context.strokeStyle = color;
      context.lineWidth = 2;
      context.stroke();
      context.closePath();

      if (!emit) { return; }
      const w = canvas.width;
      const h = canvas.height;

      socketRef.current.emit('drawing', {
        x0: x0 / w,
        y0: y0 / h,
        x1: x1 / w,
        y1: y1 / h,
        color,
      });
    };

    // ---------------- mouse movement --------------------------------------
   
    const onMouseDown = (e) => {
      drawing = true;
      current.x = e.clientX || e.touches[0].clientX;
      current.y = e.clientY || e.touches[0].clientY;
    };
    
    const onMouseMove = (e) => {
      if (!drawing) { return; }
      drawLine(current.x, current.y, e.clientX || e.touches[0].clientX, e.clientY || e.touches[0].clientY, current.color, true);
      current.x = e.clientX || e.touches[0].clientX;
      current.y = e.clientY || e.touches[0].clientY;
    };

    const onMouseUp = (e) => {
      if (!drawing) { return; }
      drawing = false;
      drawLine(current.x, current.y, e.clientX || e.touches[0].clientX, e.clientY || e.touches[0].clientY, current.color, true);
    };

    // ----------- limit the number of events per second -----------------------

    const throttle = (callback, delay) => {
      let previousCall = new Date().getTime();
      return function() {
        const time = new Date().getTime();

        if ((time - previousCall) >= delay) {
          previousCall = time;
          callback.apply(null, arguments);
        }
      };
    };

    // -----------------add event listeners to our canvas ----------------------

    canvas.addEventListener('mousedown', onMouseDown, false);
    canvas.addEventListener('mouseup', onMouseUp, false);
    canvas.addEventListener('mouseout', onMouseUp, false);
    canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false);

    // Touch support for mobile devices
    canvas.addEventListener('touchstart', onMouseDown, false);
    canvas.addEventListener('touchend', onMouseUp, false);
    canvas.addEventListener('touchcancel', onMouseUp, false);
    canvas.addEventListener('touchmove', throttle(onMouseMove, 10), false);

    // -------------- make the canvas fill its parent component -----------------
   
    const onResize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    };

    window.addEventListener('resize', onResize, false);
    onResize();

    // ----------------------- socket.io connection ----------------------------
    const onDrawingEvent = (data) => {
      const w = canvas.width;
      const h = canvas.height;
      drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color);
    }

    socketRef.current = io.connect('/');
    socketRef.current.on('drawing', onDrawingEvent);
  }, []);

  // ------------- The Canvas and color elements --------------------------

  return (
    <div>
      <canvas ref={canvasRef} className="whiteboard" />

      <div ref={colorsRef} className="colors">
        <div className="color black" />
        <div className="color red" />
        <div className="color green" />
        <div className="color blue" />
        <div className="color yellow" />
      </div>
    </div>
  );
};

export default Board;

```
---






### Make it live
Run the following commands in your app terminal.
```
npm run start
```
---
Then open another app terminal and begin your server:
```
node server/index.js
```
---

You can visit your localhost and see your whiteboard. If you open another window at the localhost, you establish the socket connection and share your drawings. 

If you were having trouble getting a canvas whiteboard working with a React front end, I hope this has helped jump-start your project. 

Stay Focused || Love your code!





## Resources:

1. [Node.js HTTP modules] (https://www.w3schools.com/nodejs/nodejs_http.asp)
2. [socket.io Docs] (https://socket.io/demos/whiteboard/)
3. [MDN web docs getContext()] (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)
4. [HTML canvas reference] (https://www.w3schools.com/tags/ref_canvas.asp)
5. [HTML 5 Canvas Tutorial Point] (https://www.C.com/html5/html5_canvas.htm)