javascripthtmlcsshtml5-canvas

How to get rid of unwanted space between bottom border and canvas on iOS/iPadOS


I have an HTML canvas on iOS/iPadOS devices shows a small gap between the canvas and the bottom border. I've fixed this problem in the past with a div by adding display: block; but that does not work on the canvas. Anyone have any idea how to remove this gap? I know that I can make the background color of the canvas to be the same as the border color but I am hoping to find a solution that fixes the actual problem.

Attached is a screen grab of the issue.

I am running iOS/iPadOS 26.0.1 and have seen the issue on iOS/iPadOS 18.6 as well.

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");

// Set canvas dimensions
canvas.width = 300;
canvas.height = 400;

// Draw a rectangle
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
canvas#myCanvas
{
    border: 4px solid black;
    display: block; /* not working like on div for iOS devices */
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
    <title>Canvas Test</title>
</head>

<body>

<h1>Canvas Test</h1>

<canvas id="myCanvas"></canvas>

</body>

</html>

enter image description here


Solution

  • This looks like an antialiasing rounding issue, caused by the default user-agent rules set on the <h1> which will set its margin-block-[start|end] to 0.67em, which doesn't map to an integral pixel value. They seem to fail to round the box position of the <canvas>'s layer in this case.

    Please report it, it seems it still affects Safari Technology Preview, so they might not know about this.

    As a workaround before they fix the issue, you could obviously override that user-agent rule so that the layout doesn't have to deal with fraction of pixels:

    var canvas = document.getElementById("myCanvas");
    var ctx = canvas.getContext("2d");
    
    // Set canvas dimensions
    canvas.width = 300;
    // I reduced the size so it fits the snippet, but the bug still occurs without the "fix"
    canvas.height = 100; 
    
    // Draw a rectangle
    ctx.fillStyle = "blue";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    canvas#myCanvas
    {
        border: 4px solid black;
        display: block; /* not working like on div for iOS devices */
    }
    h1 {
      margin-block-start: 21px;
      margin-block-end: 21px;
    }
    <h1>Canvas Test</h1>
    
    <canvas id="myCanvas"></canvas>

    On my desktop Safari 26.0.1, this snippet without the fix looks like

    A blue filled rectangle in a black stroked rectangle with a 1px white line at the bottom inside the black rectangle

    With the fix, it looks like

    The same rectangles, but this time with no 1px white line at the bottom

    But every element before your <canvas> that has it box's height computed to a fraction of pixel would need to be handled, and it doesn't seem very practical to change all the elements styles this way.

    I couldn't find a proper CSS-only workaround that would ensure the canvas is back on the pixel grid, so instead you might want to use JS to set the <canvas> margin to manually round its position:

    var canvas = document.getElementById("myCanvas");
    var ctx = canvas.getContext("2d");
    
    // Set canvas dimensions
    canvas.width = 300;
    // I reduced the size so it fits the snippet, but the bug still occurs without the "fix"
    canvas.height = 100; 
    
    // Draw a rectangle
    ctx.fillStyle = "blue";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // Workaround Safari bug with floating point margins
    canvas.style.marginTop = `${Math.floor(canvas.getBoundingClientRect().top) - canvas.getBoundingClientRect().top}px`;
    canvas#myCanvas
    {
        border: 4px solid black;
        display: block; /* not working like on div for iOS devices */
        transform: translate(0px 0px);
    }
    <h1>Canvas Test</h1>
    
    <canvas id="myCanvas"></canvas>

    But this also assumes that the content before the <canvas> is relatively static.