DEV Community

A0mineTV
A0mineTV

Posted on

Create an Interactive Eraser Tool with HTML5 Canvas 🚀

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:

  1. Support for mouse and touch events.
  2. Dynamic canvas resizing for responsive behavior.
  3. 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>
Enter fullscreen mode Exit fullscreen mode

 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, and mouseup 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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

 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)