A simple drawing grid with HTML5 part 4

June 18, 2012
A simple drawing grid with HTML5 part 4

This week, we will complete the simple drawing grid by adding support for mobile browsers. Support for mobile browsers consists of enabling interaction with the canvas with touch events rather than the mouse events that are currently used. We will not need to modify any HTML or CSS code, only our JavaScript implementation.

Understanding Touch Events

Touch interactions on mobile devices use three main events:

touchstart: Triggered when a touch point is placed on the screen.
touchmove: Triggered when a touch point is moved across the screen.
touchend: Triggered when a touch point is removed from the screen.


We’ll use these events to track touch positions and allow drawing on the canvas.

The following steps explain all required changes

Add Unified Input Handlers

We’ll create unified handlers for startDrawing, stopDrawing, and draw to ensure both touch and mouse inputs can share the same logic.

Here’s how the unified handlers look:

const startDrawing = (x, y) => {
    mouseDown = true;
    draw(x, y);
};

const stopDrawing = () => {
    mouseDown = false;
};

const draw = (x, y) => {
    if (mouseDown) {
        const adjustedX = Math.floor((x - xOffset) / penWeight);
        const adjustedY = Math.floor((y - yOffset) / penWeight);

        ctx.fillStyle = penColor;
        ctx.fillRect(adjustedX * penWeight, adjustedY * penWeight, penWeight, penWeight);
    }
};

These functions abstract the drawing logic, so we can use them for both mouse and touch inputs.

AddTouch Event Listeners

Next, we add touch event listeners (touchstart, touchmove, touchend) to the canvas. These events detect touch gestures and pass the touch coordinates to the unified handlers.

gridCanvas.addEventListener("touchstart", (e) => {
    e.preventDefault(); // Prevent scrolling on touch devices
    const touch = e.touches[0];
    startDrawing(touch.pageX, touch.pageY);
});

gridCanvas.addEventListener("touchend", (e) => {
    e.preventDefault();
    stopDrawing();
});

gridCanvas.addEventListener("touchmove", (e) => {
    e.preventDefault();
    const touch = e.touches[0];
    draw(touch.pageX, touch.pageY);
});


e.touches[0] gives the first touch point’s pageX and pageY coordinates.
e.preventDefault() stops the default browser behavior (e.g., scrolling).

Handle Mouse Events (Existing Logic)

Ensure mouse events (mousedown, mouseup, mousemove) continue to work using the same unified handlers.

gridCanvas.onmousedown = (e) => startDrawing(e.pageX, e.pageY);
gridCanvas.onmouseup = stopDrawing;
gridCanvas.onmousemove = (e) => draw(e.pageX, e.pageY);

Reattach Handlers on Canvas Reinitialization

When the canvas is reset, all event listeners must be reattached. Update the initializeCanvas() function to include the setup for both mouse and touch handlers.

function initializeCanvas() {
    gridCanvas = document.getElementById("grid");
    ctx = gridCanvas.getContext("2d");

    // Set canvas dimensions
    const viewWidth = window.innerWidth * 0.9;
    const viewHeight = window.innerHeight * 0.7;

    gridCanvas.width = viewWidth;
    gridCanvas.height = viewHeight;

    // Clear and fill the canvas
    ctx.clearRect(0, 0, viewWidth, viewHeight);
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, viewWidth, viewHeight);

    // Reattach event handlers
    setupMouseAndTouchHandlers();
}

Full Code Implementation

Here’s the complete updated JavaScript code, combining all the changes:

function initializeCanvas() {
    gridCanvas = document.getElementById("grid");
    ctx = gridCanvas.getContext("2d");

    const viewWidth = window.innerWidth * 0.9;
    const viewHeight = window.innerHeight * 0.7;

    gridCanvas.width = viewWidth;
    gridCanvas.height = viewHeight;

    ctx.clearRect(0, 0, viewWidth, viewHeight);
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, viewWidth, viewHeight);

    setupMouseAndTouchHandlers();
}

function setupMouseAndTouchHandlers() {
    const rect = gridCanvas.getBoundingClientRect();
    const xOffset = rect.left + window.scrollX;
    const yOffset = rect.top + window.scrollY;

    const startDrawing = (x, y) => {
        mouseDown = true;
        draw(x, y);
    };

    const stopDrawing = () => {
        mouseDown = false;
    };

    const draw = (x, y) => {
        if (mouseDown) {
            const adjustedX = Math.floor((x - xOffset) / penWeight);
            const adjustedY = Math.floor((y - yOffset) / penWeight);

            ctx.fillStyle = penColor;
            ctx.fillRect(adjustedX * penWeight, adjustedY * penWeight, penWeight, penWeight);
        }
    };

    gridCanvas.onmousedown = (e) => startDrawing(e.pageX, e.pageY);
    gridCanvas.onmouseup = stopDrawing;
    gridCanvas.onmousemove = (e) => draw(e.pageX, e.pageY);

    gridCanvas.addEventListener("touchstart", (e) => {
        e.preventDefault();
        const touch = e.touches[0];
        startDrawing(touch.pageX, touch.pageY);
    });

    gridCanvas.addEventListener("touchend", (e) => {
        e.preventDefault();
        stopDrawing();
    });

    gridCanvas.addEventListener("touchmove", (e) => {
        e.preventDefault();
        const touch = e.touches[0];
        draw(touch.pageX, touch.pageY);
    });
}

window.onload = initializeCanvas;

Now drawing should work on mobile devices. As an exercise you can play with the styling of controls for optimal use on a mobile devices.

Final version is located here. Note that the font awesome link was moved to the Resources area of JSFiddle.