Detecting Transparent Image Pixels with JavaScript

At some point, you may encounter a task that requires you to choose between drawing web content using DOM sprites, or using canvas. Each has its advantages and disadvantages, but for brevity I don’t go into them.

One of the immediate disadvantages of using the DOM is that, unlike canvas, it does not discern between transparent and non-transparent pixels in img elements. As a result, in a canvas-like viewport with plenty of images, you run into the immediate problem of click handlers being called when clicking transparent pixels in the images.

Here’s a potential solution to this problem, using an invisible canvas element.

(function () {
    "use strict";

    var hiddenElements = [],
        canvasContext = document.createElement("canvas")
            .getContext("2d");

    function clickedElement(event, el) {

// Store the coordinates in memory.

        var x, y, w, h,
            nextFromPoint,

// Data will be set from the canvas object, and used to
// determine whether the element is transparent, or not
// an image. In both of these cases, we hide it and
// continue.

            data,
            isTransparent;

        if (!el) {
            el = document.elementFromPoint(event.clientX,
                event.clientY);

// Let's assume the viewport is always a div layer used
// traditionally as a viewport.

            if (el.id === "viewport") {
                return null;
            }
        }

// Hide the element if we're clicking through it.
// It's important to store the elements we've hidden
// locally, since we're going to re-show them later.

        if (el instanceof HTMLImageElement) {
            x = event.clientX - el.getBoundingClientRect().left;
            y = event.clientY - el.getBoundingClientRect().top;
            w = canvasContext.canvas.width = el.width;
            h = canvasContext.canvas.height = el.height;

            canvasContext.drawImage(el, 0, 0, w, h);
            data = canvasContext.getImageData(x, y, 1, 1).data;
            isTransparent = data[3] === 0;
        } else {
            notAnImage = true;
        }

        if (isTransparent) {
            $(el).hide();
            this.hiddenElements.push(el);

            nextFromPoint = document.elementFromPoint(
                event.clientX,
                event.clientY);

            if (nextFromPoint &&
                nextFromPoint.id !== "viewport" &&
                nextFromPoint.nodeName !== "HTML")
                {

// Start again, recursively, using the same point.
// The topmost element should be hidden now, which will
// allow us to get the layer underneath it.

                    return clickedElement(event, nextFromPoint);
                }
            }
        }

        $(hiddenElements).each(function (i, hiddenEl) {
            $(hiddenEl).show();
        });

        if (isTransparent) {
            return null;
        } else {
            return el;
        }
    }

// In your viewport, handle "click" as you normally would.

    $("#viewport").on("click", function (event) {
        var clicked = clickedElement(event),
            id = $(clicked).attr("id");

        $("#output").html("Clicked: " + id);
    });
}());

Play around with this! https://jsfiddle.net/bw7botLy/ (Be sure to use 4-space indentation.)