NathanAldenSr
NathanAldenSr

Reputation: 7961

Setting font-family to sans-serif causes jQuery to calculate incorrect outerWidth

Somehow, merely setting a font-family: sans-serif style in the following document is causing li tag contents to wrap in IE 11.0.9600.16438 and FireFox 25.0.1 but not in Chrome 31.0.1650.57. I first noticed it when I noticed that some li tags were wrapping unnecessarily while using SimpleModal 1.4.4. I managed to reproduce this by adding, piece-by-piece, markup and CSS styles to a standalone document. Finally, I used jQuery's outerWidth and outerHeight methods to retrieve a calculated size, which I've confirmed is just what SimpleModal uses.

EDIT: Just to be clear, I am using the outerWidth and outerHeight results to set an explicit size on the modal dialog. This is what SimpleModal does and it's apparently crucial to the proper functioning of dialogs. My question is in the context of SimpleModal but I've narrowed down the strange behavior to jQuery.

Here's the markup without font-family: sans-serif:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title></title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
        <style>
            * { -moz-box-sizing: border-box; box-sizing: border-box; font-size: 16px; font-weight: normal; font-style: normal; font-family: sans-serif; }
            ul { margin: 0 0 0.5rem; padding: 0 0 0 2rem; list-style: disc; }
            p { margin: 0 0 0.5rem 0; padding: 0 }
            .simplemodal-wrap { height: 100%; outline: 0; width: 100%; overflow: visible; }
            .simplemodal-data { background-color: #ddd; padding: 1rem; }
        </style>
    </head>
    <body>
        <main>
            <div class="simplemodal-container" style="position: fixed; left: 25px; top: 75px; z-index: 1002;">
                <div class="simplemodal-wrap">
                    <div class="simplemodal-data">
                        <p>Lorem ipsum Lorem ipsum Lorem ipsum:</p><ul><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</li><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</li><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</li><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum </li><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</li><li>Lorem ipsum Lorem ipsum Lorem ipsum </li></ul><p>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum :</p><ul><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum </li><li>Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum </li></ul>
                    </div>
                </div>
            </div>
        </main>
        <script>
            $(function() {
                var sc = $(".simplemodal-container");
                var outerWidth = sc.outerWidth(true);
                var outerHeight = sc.outerHeight(true);

                $("main").append("<div>outerWidth(true) x outerHeight(true):<br>" + outerWidth + " x " + outerHeight + "</div>");

                sc.css({ width: outerWidth, height: outerHeight });
            });
        </script>
    </body>
</html>

The results in all three browsers look correct (here's Chrome as an example):

Chrome

Now, simply add font-family: sans-serif; to the * selector and refresh all three browsers. Suddenly, the width calculation is wrong!

IE 11 FireFox 25.0.1

Increasing the width by one pixel in IE and FireFox causes the li tags to no longer wrap.

What's going on here? Is this some kind of rounding bug with font size calculations?

EDIT: Here's the code I ended up adding to jquery.simplemodal-1.4.4.js on line 553:

// jQuery truncates subpixel width and height calculations
// Fixed incorrect wrapping in Mozilla
var userAgent = navigator.userAgent.toLowerCase();
if (userAgent.length >= 7 && userAgent.substring(0, 7) === "mozilla") {
    dw++;
    dh++;
}

Such a hack, but a necessarily one, apparently. :(

Upvotes: 1

Views: 524

Answers (1)

Boris Zbarsky
Boris Zbarsky

Reputation: 35074

What's going on is that jQuery's outerWidth API is broken by design: it returns an integer number of CSS pixels.

In a browser that does subpixel text shaping and positioning (which Firefox and IE do and Chrome does not), the width of a string of text is not going to be an integer number of CSS pixels, in general. And even in Chrome, if you have a high-DPI screen, so that one CSS pixel corresponds to two or more device pixels, the width of a string of text can end up being a non-integral number of CSS pixels.

What jQuery then does is effectively take the actual width and round down to an integer. Which means the number you get is too small if the text wasn't exactly an integer number of pixels wide.

What you probably want to do is use getBoundingClientRect() to get the actual dimensions of the box without rounding. And complain to the jQuery authors about jQuery not providing a convenient API for that. ;)

Upvotes: 1

Related Questions