Overview
In this exercise, you will create a React canvas drawing component without relying on any external libraries. This will require you to have a good understanding of React and canvas drawing basics.
Instructions for React Canvas Drawing
- Create a new React component called
CanvasDrawing
and import it into your main application. - In the
CanvasDrawing
component, create a canvas element using the HTML canvas tag and set its height and width properties to the desired size. - In the
CanvasDrawing
component, create a reference to the canvas element using theuseRef
hook. - Use the
useEffect
hook to initialize the canvas context and set the context properties such as strokeStyle, lineJoin, and lineCap. - Add event listeners to the canvas element for mouse events like mouse down, mouse move, and mouse up.
- Capture mouse coordinates with event handlers and draw lines on the canvas using context functions.
- Finally, export the
CanvasDrawing
component and use it in your main application.
Bonus Requirements
- Implement a clear functionality that allows users to clear the drawing.
- Implement a color picker tool that allows users to choose the color of the lines.
- 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. Moreover, I believe that active learning is the most effective way to learn and grow as a developer.
Firstly, grab a pen and paper, then fire up your code editor, and finally get ready to dive into the React Canvas Drawing exercise. After 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 component allows users to draw or annotate on images or canvases in various applications. Here are some examples:
- Online whiteboard: Users can use this component in an online whiteboard application to 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.
- Photo editing application: Users can use this component in a photo editing application to draw or add annotations on an image. Furthermore, with the ability to change colors, adjust thickness, erase, undo, and redo, this component can provide a simple way for users to add annotations to an image.
- Educational applications: This component lets students create and modify their work in educational apps, boosting problem-solving and note-taking skills.
- Collaboration tools: Collaboration tools use this component to enable users to collaborate on a canvas, draw, write, or annotate. This component enables remote teams to collaboratively draw with color, thickness, erase, and undo/redo features.
- Drawing games: Developers can use this component in drawing games where users need to draw or sketch something. Furthermore, this component offers a fun, interactive drawing experience with color changes, thickness adjustments, erase, undo, and redo features.
Conclusion
This canvas drawing component provides a simple and intuitive way for users to draw, write, or annotate on a canvas. Moreover, this component features color and thickness changes, erase, undo, and redo functions, making it ideal for applications like online whiteboards, photo editors, educational and collaboration tools, and drawing games. Additionally, 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 both helpful and enjoyable. Furthermore, it has deepened your understanding of React development. Therefore, keep practising and experimenting with React, and as a result, you’ll be well on your way to becoming a skilled React developer!
Boost your React skills with 25 React JavaScript Practice Exercises with Solutions hands-on challenges to master components, hooks, and more!