Shoot for the Moon

Don't be half-minded when dealing with fractions and pixels. How can we ensure all browsers interpret fractions in our CSS equally?
As CSS changes and morphs over time, we must not forget that legacy browsers remain unchanged; one gotcha that a legacy browser might snipe you with, if left unattended, is decimals.
A confusing subject for the uninitiated (myself included), decimals in CSS behave in a way that might not be straightforward to some, especially when you consider the variety of browsers and their individual behaviours.
A Bit of Prerequisite Information Permalink ¶
It’s probably worth refreshing yourself on the various rounding methods used in CSS in various browsers. Alex Kilgour wrote an excellent article on the subject, Browser Rounding and Fractional Pixels, that’s more than worth reading through and bookmarking, if only for his concise table of rounding methods used by browsers and when they’re used. I’ve summarised these different rounding methods below, but I still recommend checking out Alex’s article.
- truncate to x decimals
-
Strips all but the first x characters after the decimal.
Let x = 2
12.3456%
→12.34%
- round to x decimals
-
Rounds the figure to x decimals.
Let x = 2
12.3456%
→12.35%
- nearest integer
-
Same as
round to x decimals
but rounds to the nearest integer (whole number).
12.3456%
→12%
12.5000%
→13%
- down
-
Same as
nearest integer
but always rounds down to the nearest integer.
12.3456%
→12%
12.9999%
→12%
- sub-pixel rendering
- This is the most complicated of the different methods of dealing with decimals in CSS. I will freely admit I know very little about what’s going on with sub-pixel rendering, but have drawn up a quick demo to show a little bit about how it works.
Sorry, this code snippet failed to load, but you can still check it out over on CodePen!
While the width
of each box in the above demo is technically 133.3333px
, sub-pixel rendering comes into play, and its behaviour might be surprising. You might expect that the width
of each box would be rounded individually, creating three 133px
-wide boxes, leaving one extra pixel of the full 400px
-wide .parent
unaccounted for.
However, what is happening, as far as I can tell, is that the browser creates a tally of the leftover 0.3333px
from each of the three boxes and adds that one extra pixel of width
to one of the three boxes. The exact mechanics of how this happens are a bit of a mystery to me (why does the middle box receive the extra pixel?), but the outcome makes some rhyme and reason.
But let’s not concern ourselves with the mechanics of sub-pixel rendering for now, and focus on legacy browsers that employ the less accurate methods of CSS rounding, such as down
or nearest integer.
An Example Permalink ¶
Let’s look at an example where we’re setting a percentage-based value that includes decimals. Please ignore the glaringly obvious magic number in this example!
.parent {
width: 1337px;
}
.child {
width: 60.029%;
}
.parent
width
is set to1337px
.child
width
is set to60.029%
, as our targetwidth
is803px
1337px ÷ 100 × 60.029 = 802.58773px
Modern browsers will utilise sub-pixel rendering to render a pixel value containing decimals; however, older browsers, like IE8, will truncate the percentage-based value to only two decimal places! This spells trouble in our particular case:
1337px ÷ 100 × 60.02 = 802.4674px
Even the above value is rounded to the wrong target value by a modern browser.
Due to discrepancies between browsers, we can’t be sure whether a value will receive sub-pixel rendering, be truncated, be rounded to the nearest integer, or even be rounded down (floored) to the nearest integer!
As a result, more often than not, I recommend overshooting your target value with your fraction, whether it be a percentage, em, or rem fraction
. The reason for overshooting is such that any browser’s method of rounding decimals will achieve your target value.
Brass Tacks Permalink ¶
So let’s use the running example, and modify it to match these conditions and ensure that, no matter the rounding method used by the browser, the end-result pixel value is consistent.
.parent {
width: 1337px;
}
.child {
width: 60.06%;
}
.parent
width
is set to1337px
.child
width
is set to60.06%
1337px ÷ 100 × 60.06 = 803.0022px
Because the worst truncation that will occur is to 2
decimal places, our value of 60.06%
will satisfy each rounding method, and our target value of 803px
will be achieved cross-browser.
It’s also worth noting that a percentage-based value of 60.059%
, ever-so-slightly less than 60.06%
, will result in a computed value of 802.9888px
. This satisfies almost every method of rounding, but it still fails when rounding down.
By making sure our computed value overshoots the target value, that is to say that the decimal value is slightly greater than the integer value, we satisfy the conditions to round down
to our target value.
The Takeaway Permalink ¶
When creating fractions resulting in decimals in CSS, make sure that your computed value overshoots your target value if you have to support legacy browsers.