While working on a side project this week, I needed to be able to highlight the margin, border, and padding of HTML elements like the chrome inspector does. Turns out you can't do it with CSS, because that would be far too convenient. I lean back, breathe deeply, and ponder my career choice once again and open up VSCode.

Grabbing Coordinates

So styling with CSS is impossible, but you can tell where the element should be on the page. If you get an element in Javascript (using something like querySelectorAll), you can get its bounding positions (top, bottom, left, right) using Element.getBoundingClientRect(). These coordinates represent the box that the element is contained in.

But what about border and margin?

Since the bounding rect doesn't include the margin and border, we have to get this from the element's style. To do this, we can use Window.getComputedStyle(element). This gives us string versions of the style properties we need, like marginLeft, paddingLeft, and borderLeft. From here, just convert the properties to numbers we can use.

// Ex, turns '12px' to the number 12
function pxToNumber(px) {
    return parseInt(px.replace('px', ''));
}

Drawing the outline

Now that we have the coordinates of the element, and how large the border and margins of an element are, we know where we could draw the rectangles around the element. To draw the rectangles in the right location, we can use a canvas element, which can be used to programmatically draw shapes on our page.

function getDocumentHeightAndWidth() {
    const body = document.body;
    const html = document.documentElement;

    const height = Math.max(body.scrollHeight, body.offsetHeight,
        html.clientHeight, html.scrollHeight, html.offsetHeight);
    const width = document.body.offsetWidth;
    return { height, width };
}

function createCanvas() {
    var canvas = document.createElement('canvas'); //Create a canvas element
    //Set canvas width/height
    setCanvasWidthAndHeight(canvas);
    //Position canvas
    canvas.style.position = 'absolute';
    canvas.style.left = '0';
    canvas.style.top = '0';
    canvas.style.zIndex = '100000';
    canvas.style.pointerEvents = 'none'; //Make sure you can click 'through' the canvas
    document.body.appendChild(canvas); //Append canvas to body element
    const context = canvas.getContext('2d');
    context.globalAlpha = 0.5;
    return { canvas, context: context };
}

function setCanvasWidthAndHeight(canvas) {
    const { height, width } = getDocumentHeightAndWidth();
    canvas.style.width = `${width}`;
    canvas.style.height = `${height}`;
    canvas.width = width;
    canvas.height = height;
}

This canvas overlays the entire page, so we can draw wherever there are visible elements. It sits over all the elements, but to avoid clicking on it, its pointerEvents style property is set to none, so all clicks will pass through to the underlying page.

Time to Draw

To show the margins, we need to draw 4 rectangles around an element to create what looks like a thick border around the element. This technique can then be used to draw the padding and border as well.

function drawMarginBorderPadding(ctx, element) {
    const style = getComputedStyle(element);
    let { top, left, right, bottom, width, height } = element.getBoundingClientRect();
    const { marginTop, marginBottom, marginLeft, marginRight, paddingTop, paddingBottom, paddingLeft, paddingRight, borderBottomWidth, borderTopWidth, borderLeftWidth, borderRightWidth, display } = style;

    top = top + window.scrollY;
    left = left + window.scrollX;
    bottom = bottom + window.scrollY;
    right = right + window.scrollX;

    const numMarginTop = pxToNumber(marginTop);
    const numMarginBottom = pxToNumber(marginBottom);
    const numMarginLeft = pxToNumber(marginLeft);
    const numMarginRight = pxToNumber(marginRight);
    const numPaddingTop = pxToNumber(paddingTop);
    const numPaddingBottom = pxToNumber(paddingBottom);
    const numPaddingLeft = pxToNumber(paddingLeft);
    const numPaddingRight = pxToNumber(paddingRight);
    const numBorderTop = pxToNumber(borderTopWidth);
    const numBorderBottom = pxToNumber(borderBottomWidth);
    const numBorderLeft = pxToNumber(borderLeftWidth);
    const numBorderRight = pxToNumber(borderRightWidth);

    const marginHeight = height + numMarginBottom + numMarginTop;

    // DRAW MARGIN
    ctx.fillStyle = '#ffa50094';
    // Top margin rect
    ctx.fillRect(left, top - numMarginTop, width, numMarginTop);
    // Bottom margin rect
    ctx.fillRect(left, bottom, width, numMarginBottom);
    // Left margin rect
    ctx.fillRect(left - numMarginLeft, top - numMarginTop, numMarginLeft, marginHeight);
    // Right margin rect
    ctx.fillRect(right, top - numMarginTop, numMarginRight, marginHeight);

    const paddingWidth = width - numBorderLeft - numBorderRight;
    const paddingHeight = height - numPaddingBottom - numPaddingBottom - numBorderTop - numBorderBottom;

    // DRAW PADDING
    ctx.fillStyle = '#00800040';
    // Top padding rect
    ctx.fillRect(left + numBorderLeft, top + numBorderTop, paddingWidth, numPaddingTop);
    // Bottom padding rect
    ctx.fillRect(left + numBorderLeft, bottom - numPaddingBottom - numBorderBottom, paddingWidth, numPaddingBottom);
    // Left padding rect
    ctx.fillRect(left + numBorderLeft, top + numPaddingTop + numBorderTop, numPaddingLeft, paddingHeight);
    // Right padding rect
    ctx.fillRect(right - numPaddingRight - numBorderRight, top + numPaddingTop + numBorderTop, numPaddingRight, paddingHeight);

    const borderHeight = height - numBorderTop - numBorderBottom;

    // DRAW BORDER
    ctx.fillStyle = '#0000ff1a';
    // Top border rect
    ctx.fillRect(left, top, width, numBorderTop);
    // Bottom border rect
    ctx.fillRect(left, bottom - numBorderBottom, width, numBorderBottom);
    // Left border rect
    ctx.fillRect(left, top + numBorderTop, numBorderLeft, borderHeight);
    // Right border rect
    ctx.fillRect(right - numBorderRight, top + numBorderTop, numBorderRight, borderHeight);
}

Putting it all together

When the page initializes, we want to query the elements we want to highlight, draw their bounding styles, and redraw them when the window resizes.

const rowComponentSelector = ".higlightmeplease";

function findComponentsByDefinition(querySelector) {
    return document.querySelectorAll(querySelector);
}

const { canvas, context: ctx } = createCanvas();
const rows = findComponentsByDefinition(rowComponentSelector);


function drawSelectedElements(elements) {
    const { height, width } = getDocumentHeightAndWidth();
    ctx.clearRect(0, 0, width, height);
    setCanvasWidthAndHeight(canvas);
    elements.forEach((e) => {
        drawMarginBorderPadding(ctx, e);
    });
}

drawSelectedElements(rows);

window.addEventListener('resize', () => {
    drawSelectedElements(rows);
});

Demo Time

I'm a row!
I'm a row with different sized borders!

Full source code here: https://gist.github.com/awestbro/e668c12662ad354f02a413205b65fce7

The project I'm working on is dedicated to making front end web development much simpler. Subscribe for updates!