React Canvas Drawing - JavaScript Exercise #15

React Canvas Drawing - JavaScript Exercise #15

Build a React canvas drawing component without using any external libraries.

ยท

5 min read

Overview

In this exercise, you will create a canvas drawing component in React without relying on any external libraries. This will require you to have a good understanding of React and canvas drawing basics.

Instructions

  1. Create a new React component called CanvasDrawing and import it into your main application.

  2. In the CanvasDrawing component, create a canvas element using the HTML canvas tag and set its height and width properties to the desired size.

  3. In the CanvasDrawing component, create a reference to the canvas element using the useRef hook.

  4. Use the useEffect hook to initialize the canvas context and set the context properties such as strokeStyle, lineJoin, and lineCap.

  5. Add event listeners to the canvas element for mouse events like mouse down, mouse move, and mouse up.

  6. Implement the event handlers to capture the mouse coordinates and use the context functions to draw lines on the canvas.

  7. Finally, export the CanvasDrawing component and use it in your main application.

Bonus Requirements

  1. Implement a clear functionality that allows users to clear the drawing.

  2. Implement a color picker tool that allows users to choose the color of the lines.

  3. Implement an undo and redo functionality that allows users to undo or redo their drawings.

Before you dive into the final output, I want to encourage you to take some time to work through the exercise yourself. I believe that active learning is the most effective way to learn and grow as a developer.

So, grab a pen and paper, fire up your code editor, and get ready to dive into the React Canvas Drawing exercise. Once you have completed the exercise, feel free to return to this blog post to compare your solution to mine.

Output for the Canvas Drawing exercise

import { useState, useRef, useEffect } from 'react';

function CanvasDrawing() {
  const [color, setColor] = useState('#000000');
  const [thickness, setThickness] = useState(5);
  const [isDrawing, setIsDrawing] = useState(false);
  const [lastX, setLastX] = useState(0);
  const [lastY, setLastY] = useState(0);
  const [undoStack, setUndoStack] = useState([]);
  const [redoStack, setRedoStack] = useState([]);
  const canvasRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    ctx.lineWidth = thickness;
    ctx.strokeStyle = color;

    function startDrawing(e) {
      setIsDrawing(true);
      setLastX(e.clientX - canvas.offsetLeft);
      setLastY(e.clientY - canvas.offsetTop);
      setUndoStack([...undoStack, canvas.toDataURL()]);
      setRedoStack([]);
    }

    function draw(e) {
      if (!isDrawing) return;
      if (color === '#ffffff') {
        // Use the eraser tool
        ctx.globalCompositeOperation = 'destination-out';
        ctx.strokeStyle = 'rgba(0,0,0,0)';
        ctx.lineWidth = thickness * 2;
      } else {
        // Use the drawing tool
        ctx.globalCompositeOperation = 'source-over';
        ctx.strokeStyle = color;
        ctx.lineWidth = thickness;
      }
      ctx.beginPath();
      ctx.moveTo(lastX, lastY);
      ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
      ctx.stroke();
      setLastX(e.clientX - canvas.offsetLeft);
      setLastY(e.clientY - canvas.offsetTop);
    }

    function stopDrawing() {
      setIsDrawing(false);
    }

    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', stopDrawing);
    canvas.addEventListener('mouseout', stopDrawing);

    return () => {
      canvas.removeEventListener('mousedown', startDrawing);
      canvas.removeEventListener('mousemove', draw);
      canvas.removeEventListener('mouseup', stopDrawing);
      canvas.removeEventListener('mouseout', stopDrawing);
    };
  }, [color, thickness, isDrawing, lastX, lastY, undoStack]);

  function handleColorChange(e) {
    setColor(e.target.value);
  }

  function handleThicknessChange(e) {
    setThickness(e.target.value);
  }

  function handleClear() {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    setUndoStack([]);
    setRedoStack([]);
  }

  function handleUndo() {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    if (undoStack.length > 0) {
      const lastDataURL = undoStack.pop();
      setRedoStack([...redoStack, canvas.toDataURL()]);
      const img = new Image();
      img.onload = () => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(img, 0, 0);
      };
      img.src = lastDataURL;
    }
  }

  function handleRedo() {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    if (redoStack.length > 0) {
      const lastDataURL = redoStack.pop();
      setUndoStack([...undoStack, canvas.toDataURL()]);
      const img = new Image();
      img.onload = () => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(img, 0, 0);
      };
      img.src = lastDataURL;
    }
  }

  return (
    <>
      <div className="controls">
        <label htmlFor="color">Color:</label>
        <input type="color" id="color" value={color} onChange={handleColorChange} />
        <label htmlFor="thickness">Thickness:</label>
        <input type="range" id="thickness" min="1" max="50" value={thickness} onChange={handleThicknessChange} />
        <button onClick={handleClear}>Clear</button>
        <button onClick={handleUndo} disabled={undoStack.length === 0}>Undo</button>
        <button onClick={handleRedo} disabled={redoStack.length === 0}>Redo</button>
      </div>
      <canvas ref={canvasRef} width="800" height="600" style={{ border: "1px solid #000000" }}></canvas>
    </>
  );
}

export default CanvasDrawing;
import React from "react";
import CanvasDrawing from "./CanvasDrawing";

const App = () => {
  return (
    <div>
      <h1>Canvas Drawing Example</h1>
      <CanvasDrawing />
    </div>
  );
};

export default App;

This canvas drawing component can be used in various applications that require the user to draw or annotate on an image or canvas. Here are some examples:

  1. Online whiteboard: This component can be used in an online whiteboard application where users can draw, write, or annotate on a canvas. With the ability to change colors, thickness, erase, undo, and redo, this component can provide a powerful whiteboard experience for users.

  2. Photo editing application: This component can be used in a photo editing application where users can draw or add annotations on an image. With the ability to change colors, thickness, erase, undo, and redo, this component can provide a simple and intuitive way for users to add annotations or highlights to an image.

  3. Educational applications: This component can be used in educational applications where users can draw or write on a canvas. With the ability to change colors, thickness, erase, undo, and redo, this component can provide a great tool for students to solve problems, draw diagrams, or take notes.

  4. Collaboration tools: This component can be used in collaboration tools where users need to collaborate on a canvas, draw, write, or annotate. With the ability to change colors, thickness, erase, undo, and redo, this component can provide a collaborative drawing experience for teams working remotely.

  5. Drawing games: This component can be used in drawing games where users need to draw or sketch something. With the ability to change colors, thickness, erase, undo, and redo, this component can provide a fun and interactive drawing experience for users.

this canvas drawing component provides a simple and intuitive way for users to draw, write, or annotate on a canvas. With the ability to change colors, thickness, erase, undo, and redo, this component can be used in various applications such as online whiteboards, photo editing applications, educational applications, collaboration tools, and drawing games. By customizing this component to fit your needs, you can provide a powerful and engaging drawing experience for your users.

Thanks for taking this JavaScript exercise!

I hope you found this exercise helpful and enjoyable, and that it has deepened your understanding of React development. Keep practising and experimenting with React, and you'll be well on your way to becoming a skilled React developer!

If you have any questions or comments, feel free to reach out to me on Twitter(@rajeshtomjoe), or follow me for more updates.

And if you'd like to receive more exercise on JavaScript, be sure to subscribe to my blog.

ย