javascripthtmlcsshtml5-canvas

simingly unlinked CSS changes impact value of window.innerWidth and window.innerHeight on chrome


Everything works fine on a regular chrome page on a computer, but as soon as I toggle the device toolbar and preview my website from let's say an iphone 12 (any device in portrait mode works), the value given by window.innerWidth and window.innerHeight are (although at the correct ratio) bigger than the intended resolution, which weirdly doesn't happen on devices in landscape mode (such as a nest hub for example),but also works fine in portrait mode when I go back to preview my website on a normal chrome window.

Here is a minimal HTML page that reproduces the bug:

        let canvas = document.getElementById('canvas');
        // set canvas resolution
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        console.log(window.innerWidth, window.innerHeight);
        // fill canvas with black
        let ctx = canvas.getContext('2d');
        ctx.fillStyle = 'black';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
 html,
        body {
            font-family: "Helvetica";
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        input {
            z-index: 1;
            position: absolute;
            top: 50%;
            left: 50%;
            margin: 0;
            background: none;
            outline: none;
            border: none;
            color: white;
            font-size: 48px;
            font-weight: bolder;
            text-align: center;
            transform: translateX(-50%) translateY(-50%);
            mix-blend-mode: difference;
        }

        #canvas {
            position: absolute;
            top: 0;
            left: 0;
            padding: 0%;
            margin: 0%;
            z-index: -1;
        }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>
<body>
    <canvas id="canvas"></canvas>
    <input id="input" value="Hello, World!"></input>
  
</body>
</html>

For some reasons, removing the font-size: 48px line removes the issue; why is some CSS affecting stuff from javascript?? This doesn't happen in firefox and the correct value is always given, although you can still scroll in the page like you can on the bigger canvas created in chrome.


Solution

  • The issue here has to do with the difference in how browsers calculate these values when there is overflow of the body element, even if it is styled as hidden. The reason that removing the font-size: 48px rule solves the problem is that setting an absolute size for the text causes the input element to grow wider than the body element has room for at the device sizes you're testing at, which seems to confuse Chrome. If you check portrait mode on a large enough device like a tablet you see the accurate values getting logged. For whatever reason, Firefox is able to ignore overflow outside of the window while calculating innerWidth and innerHeight but Chrome struggles. (Oddly, the width that Chrome reports is still less than the width of the overflowing element...)

    One solution would be to change your CSS to use a relative size for your text, like ems. In that case the input does not overflow the body and Chrome has no issues. However by virtue of being relative this means the text will scale with the screen size, which may or may not be desired behavior. If you absolutely need the text to be displayed at an absolute size you might want to check out this answer on a different question about Chrome being weird about window sizes and try a different way of getting the viewport dimensions.

    TL;DR Using px instead of something like % or em is causing overflow of the body element. Try changing to a relative text size, and if that doesn't work or isn't realistic you may have to rely on a different method of determining screen size.

    I hope one of those solutions helps!

    EDIT: Apparently Chrome has a special way of actually retrieving just the visible viewport, window.visualViewport.width (See the docs here) However, messing around with it a little, setting the canvas size by it seems to make the whole page act a little strangely so it might require some tweaking to get it to do what you need.