Inspired by skribbl.io, worked with Nhat Do to create our own version of an online multiplayer pictionary game.
The site consists of 4 major components:

  • Canvas
  • Messaging client
  • Active player list
  • Game

Ended up designing a wireframe that's a bit too serious for such a lighthearted game, but wanted to focus more on programming functionality and decided to just follow through with matching UI elements.
An unexpected challenge was just visually showing shifts when changing tools. In order to both visually show which tool was un/selected/on-hover and limit functionality to their respective tools, switching states resulted in a lot of repeat code. This was circumvented through the use of 2 helper methods and specific file naming standards.
   changeImage(element, imageLink){
        let img = document.getElementById(`${element}`);
        img.setAttribute('src', imageLink);

        this.setState({ mode: `${mode}` });
        this.changeImage(`tool_${mode}`, `/icons/tools_${mode}_select.png`);

    <div >
        <img id='tool_draw' className='toolIcon' src='/icons/tools_draw.png'
            onMouseOver={(this.state.mode !== 'draw') ?
                () => this.changeImage('tool_draw', '/icons/tools_draw_hover.png')
                : function () {}}
                (this.state.mode !== 'draw') ? () => this.changeImage('tool_draw', '/icons/tools_draw.png') : this.changeImage('tool_draw', '/icons/tools_draw_select.png')}
            onClick={() => {
                this.props.returnToBrush()}} />

In order to have an actual playable game, it was necessary to have real time updates of both images and messages to any connected clients/players, which was capable through websockets. In order to convert the canvas brush strokes into a form that could be sent using sockets, canvas's built in .toDataURL was used to save and convert the image on the canvas into a string form, saved to the state, and emitted to the server. On mounting, each client then received the broadcast image data, assigned as a new Image(), and drawn to the size of the canvas.

This project was also my introduction into using websockets for realtime updates and although it took me several head-banging hours to understand how to integrate them into react, but the result was pretty gratifying!!

        if (this.state.mode === 'erase') this.setState({strokeStyle: '#fff'});

        if ((this.state.mode ==='erase' || this.state.mode === 'draw') 
        && this.state.isDrawing && this.drawArea.current.contains(e.target)) {
            let canvas = document.getElementById('canvas');
            let ctx = canvas.getContext('2d');
            ctx.strokeStyle = this.state.strokeStyle;
            ctx.lineJoin = 'round';
            ctx.lineCap = 'round';
            ctx.lineWidth = this.state.lineWidth;
            ctx.moveTo(this.lastX, this.lastY);
            ctx.lineTo(e.offsetX, e.offsetY);
            [this.lastX, this.lastY] = [e.offsetX, e.offsetY];

        document.addEventListener('mousedown', this.handleMouseDown);
        document.addEventListener('mousemove', this.draw);
        document.addEventListener('click', this.eyedropper);

        this.socket.on('sketch update', (image) => {
            let canvas = document.getElementById('canvas');
            let ctx = canvas.getContext('2d');
            var myImg = new Image();
            let imgsrc = image;
            myImg.onload = function () {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.drawImage(myImg, 0, 0);
            myImg.src = imgsrc;
      let canvas = document.getElementById('canvas');
      var dataURL = canvas.toDataURL();
      this.setState({ saveState: dataURL });

      this.socket.emit('sketch update', this.state.saveState);

Resources I referenced if you also want to make your own :)!
  • Websockets: https://socket.io/get-started/chat/
  • HTML Canvas (Day 8): https://javascript30.com/