Create an Interactive Eraser Tool with HTML5 Canvas
Ever wanted to build a tool that allows users to erase parts of an image interactively on your website? In this article, I’ll walk you through the details of creating an eraser tool using HTML5 Canvas, JavaScript, and some event handling magic.
What Is This Project About ?
The project involves developing a web-based eraser tool that lets users:
- Upload an image.
- Erase parts of it using mouse or touch gestures.
- Download the modified image.
This tool is perfect for web applications that require creative user input, such as digital whiteboards, drawing apps, or interactive educational tools.
How It Works
The eraser functionality is built using the canvas
element and JavaScript's globalCompositeOperation
. This enables us to "erase" parts of the canvas by blending pixels.
Key features include:
- Support for mouse and touch events.
- Dynamic canvas resizing for responsive behavior.
- Efficient rendering using throttled updates for smooth interaction.
The Code: A Quick Overview
1. Setting Up the HTML
Here’s the basic structure of the page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Eraser Tool</title>
<style>
canvas {
width: 100%;
max-width: 640px;
margin: 0 auto;
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<input id="uploadImage" type="file">
<button id="submit">Start Erasing</button>
<button id="download">Download</button>
<script src="eraser.js"></script>
</body>
</html>
 2. The Eraser Class
The core functionality is encapsulated in a modular Eraser
class. Here's a high-level explanation of what it does:
Initialize the Canvas
: Dynamically resize the canvas based on the uploaded image dimensions.Handle Events: Bind
touchstart
,mousemove
, andmouseup
events for erasing.
Here’s the main logic:
((exports) => {
const { document } = exports;
const hastouch = 'ontouchstart' in exports;
const tapstart = hastouch ? 'touchstart' : 'mousedown';
const tapmove = hastouch ? 'touchmove' : 'mousemove';
const tapend = hastouch ? 'touchend' : 'mouseup';
let x1, y1, x2, y2;
const options_type = {
tap_start_x1: 400,
tap_start_y1: 30,
tap_move_x2: 900,
tap_move_y2: 25
};
class Eraser {
constructor(canvas, imgUrl, options = options_type) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.imgUrl = imgUrl;
this.timer = null;
this.lineWidth = 55;
this.gap = 10;
this.options = options;
}
init(args) {
Object.assign(this, args);
const img = new Image();
this.canvasWidth = this.canvas.width = Math.min(document.body.offsetWidth, 640);
img.crossOrigin = "*";
img.onload = () => {
this.canvasHeight = (this.canvasWidth * img.height) / img.width;
this.canvas.height = this.canvasHeight;
this.ctx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight);
this.initEvent();
};
img.src = this.imgUrl;
}
initEvent() {
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
this.ctx.lineWidth = this.lineWidth;
this.ctx.globalCompositeOperation = 'destination-out';
this.tapMoveHandler = this.onTapMove.bind(this);
this.tapStartHandler = this.onTapStart.bind(this);
this.tapEndHandler = this.onTapEnd.bind(this);
this.tapStartHandler();
this.tapEndHandler();
}
onTapStart() {
x1 = this.options.tap_start_x1 - this.canvas.offsetLeft;
y1 = this.options.tap_start_y1 - this.canvas.offsetTop;
this.ctx.beginPath();
this.ctx.arc(x1, y1, 1, 0, 2 * Math.PI);
this.ctx.fill();
this.ctx.stroke();
this.tapMoveHandler();
}
onTapMove() {
if (!this.timer) {
this.timer = setTimeout(() => {
x2 = this.options.tap_move_x2 - this.canvas.offsetLeft;
y2 = this.options.tap_move_y2 - this.canvas.offsetTop;
this.ctx.moveTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
x1 = x2;
y1 = y2;
this.timer = null;
}, 40);
}
}
onTapEnd() {
let count = 0;
const imgData = this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight);
for (let x = 0; x < imgData.width; x += this.gap) {
for (let y = 0; y < imgData.height; y += this.gap) {
const i = (y * imgData.width + x) * 4;
if (imgData.data[i + 3] > 0) {
count++;
}
}
}
if (count / (imgData.width * imgData.height / (this.gap ** 2)) < 0.6) {
setTimeout(() => {
this.removeEvent();
document.body.removeChild(this.canvas);
this.canvas = null;
}, 40);
} else {
this.tapMoveHandler();
}
}
removeEvent() {
this.tapStartHandler();
this.tapEndHandler();
this.tapMoveHandler();
}
}
exports.Eraser = Eraser;
})(window);
3. Tying It All Together
We use the Eraser
class in conjunction with user input:
<script>
let canvas = document.getElementById('canvas')
const buttonSubmit = document.getElementById("submit")
buttonSubmit.addEventListener("click", e => {
e.preventDefault()
const file = document.getElementById("uploadImage").files[0]
let url = window.URL || window.webkitURL
const image = url.createObjectURL(file)
const canvas = document.querySelector('#canvas'),
eraser = new Eraser(canvas, image, {
"tap_start_x1": 400,
"tap_start_y1": 30,
"tap_move_x2": 900,
"tap_move_y2": 25
});
eraser.init();
//
document.querySelector('#stateErased')
.setAttribute('data-erased', "true")
})
let downloadButton = document.querySelector('button#download')
downloadButton.addEventListener('click', (e) => {
e.preventDefault()
// Download canvas like an image (png, jpg)
const div = document.querySelector('[data-image-erased]')
let image = canvas.toDataURL("image/png")
div.setAttribute('data-image-erased', image)
})
</script>
 Why Use This Tool?
This eraser tool can serve various purposes:
Interactive image editing: Allow users to customize or modify images directly on your site.
Educational tools: Build interactive learning modules for art or science.
Gaming: Create simple games involving touch or drag gestures.
Future Improvements
Here are some enhancements that can be added:
Undo/Redo functionality.
Customizable eraser sizes.
Mobile-friendly touch gestures.
Final Thoughts
Building an interactive canvas tool like this is an exciting project that blends HTML5 Canvas, JavaScript, and a touch of creativity. Whether you're building this for fun or as part of a larger web app, the possibilities are endless !
Top comments (0)