Published on March 3, 2022 Updated on June 13, 2024

Create a drawing app with React

Today, we will be making this drawing app with React.

Contents

Creating the react app

First, we need to create a new react app, so I will run this in the terminal.

npx create-react-app drawing-app

Now this will create a new folder called drawing-app. Now we change our directory.

cd drawing-app

If we run the ls command you should see this.

We’re ready to go ?

Starting the app

Now we have our app ready, we will start the app.

npm run start

Now open localhost:3000 in your browser. You should see something like this.

Now we open our editor and remove all the code in App.js and put this code.

// Import dependencies
import { useEffect, useRef, useState } from 'react';

// Our code
export default function App() {
	return <div></div>;
}

First, we will create the canvas.

<canvas></canvas>

Then, we will use the useRef hook to select the canvas element.

const canvasRef = useRef(null);
//.....
<canvas ref={canvasRef}></canvas>;

Now we have our canvas, now we will add the functionality to draw it. To do this we will create 2 functions one for setting the mouse position and one for drawing. Before we do that, we need to set our states.

const [mouseData, setMouseData] = useState({ x: 0, y: 0 });
const [canvasCTX, setCanvasCTX] = useState(null);

// Set the canvas ctx as the state
useEffect(() => {
	const canvas = canvasRef.current; // Select the canvas element
	const ctx = canvas.getContext('2d'); // The canvas context
	canvas.width = window.innerWidth; // Set width of the canvas to the width of the screen
	canvas.height = window.innerHeight; // Set height of the canvas to the height of the screen
	setCanvasCTX(ctx); // Finally, set the state
}, [canvasRef]); // Do this everytime the canvas element is changed

The function to save the position

const SetPos = (e) => {
	// The e variable is the event
	setMouseData({
		x: e.clientX, // Mouse X position
		y: e.clientY // Mouse Y position
	});
};

Now we add event listeners to our canvas.

<canvas
   ref={canvasRef}
   onMouseEnter={(e) => SetPos(e)}
   onMouseMove={(e) => SetPos(e)}
   onMouseDown={(e) => SetPos(e)}
</canvas>

Now every time we move the mouse, the state gets updated.

Function to draw

Now before we create the Draw function, we need to save 2 states.

  1. Color of the pen
  2. Size of the pen

We can use the hook useState again.

const [color, setColor] = useState('#000000'); // Default color is black
const [size, setSize] = useState(10); // Default size is 10

Now we have our states. Let’s create the function now.

const Draw = (e) => {
	if (e.buttons !== 1) return; // The left mouse button should be pressed
	const ctx = canvasCTX; // Our saved context
	ctx.beginPath(); // Start the line
	ctx.moveTo(mouseData.x, mouseData.y); // Move the line to the saved mouse location
	setMouseData({
		x: e.clientX, // Update the mouse location
		y: e.clientY // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
	});
	ctx.lineTo(e.clientX, e.clientY); // Again draw a line to the mouse postion
	ctx.strokeStyle = color; // Set the color as the saved state
	ctx.lineWidth = size; // Set the size to the saved state
	// Set the line cap to round
	ctx.lineCap = 'round';
	ctx.stroke(); // Draw it!
};

Now we add our event listener to the canvas.

<canvas ref="{canvasRef}" onMouseEnter="{(e)" ="">
	SetPos(e)} onMouseMove={(e) => {SetPos(e);Draw(e)}} onMouseDown={(e) => SetPos(e)} ></canvas
>

Now save the file and open localhost:3000. Now you can draw some stuff. But wait, we need to add the controls.

<div
	className="controlpanel"
	style={{
		position: 'absolute',
		top: '0',
		left: '0',
		width: '100%'
	}}
>
	<input
		type="range"
		value={size}
		max={40}
		onChange={(e) => {
			setSize(e.target.value);
		}}
	/>
	<input
		type="color"
		value={color}
		onChange={(e) => {
			setColor(e.target.value);
		}}
	/>
	<button
		onClick={() => {
			const ctx = canvasCTX;
			ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
		}}
	>
		Clear
	</button>
</div>

Now we have made our drawing app! ?That’s all for now. See you next time. ?

And if you need to copy-paste the full code here it is.

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

function App() {
	const [mouseData, setMouseData] = useState({ x: 0, y: 0 });
	const canvasRef = useRef(null);
	const [canvasCTX, setCanvasCTX] = useState(null);
	const [color, setColor] = useState('#000000');
	const [size, setSize] = useState(10);

	useEffect(() => {
		const canvas = canvasRef.current;
		const ctx = canvas.getContext('2d');
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;
		setCanvasCTX(ctx);
	}, [canvasRef]);

	const SetPos = (e) => {
		setMouseData({
			x: e.clientX,
			y: e.clientY
		});
	};

	const Draw = (e) => {
		if (e.buttons !== 1) return;
		const ctx = canvasCTX;
		ctx.beginPath();
		ctx.moveTo(mouseData.x, mouseData.y);
		setMouseData({
			x: e.clientX,
			y: e.clientY
		});
		ctx.lineTo(e.clientX, e.clientY);
		ctx.strokeStyle = color;
		ctx.lineWidth = size;
		// Set the line cap to round
		ctx.lineCap = 'round';
		ctx.stroke();
	};

	return (
		<div>
			<canvas
				ref={canvasRef}
				onMouseEnter={(e) => SetPos(e)}
				onMouseMove={(e) => SetPos(e)}
				onMouseDown={(e) => SetPos(e)}
				onMouseMove={(e) => Draw(e)}
			></canvas>

			<div
				className="controlpanel"
				style={{
					position: 'absolute',
					top: '0',
					left: '0',
					width: '100%'
				}}
			>
				<input
					type="range"
					value={size}
					max={40}
					onChange={(e) => {
						setSize(e.target.value);
					}}
				/>
				<input
					type="color"
					value={color}
					onChange={(e) => {
						setColor(e.target.value);
					}}
				/>
				<button
					onClick={() => {
						const ctx = canvasCTX;
						ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
					}}
				>
					Clear
				</button>
			</div>
		</div>
	);
}

export default App;

Something wrong or just found a typo? Edit this page on GitHub and make a PR!

Comments