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 !

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, weโ€™ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, weโ€™ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, โ€œnot bad.โ€

Fixing code doesnโ€™t have to be the worst part of your day. Learn how Sentry can help.

Learn more

๐Ÿ‘‹ Kindness is contagious

Please leave a โค๏ธ or a friendly comment on this post if you found it helpful!

Okay