While working on a project, I discovered something challenging recently that shed a lot of light on relatively obscure bugs I have encountered in the past. I felt it was obscure enough to document something for the team, and hopefully prevent some odd bugs from occurring (or take less time to diagnose if they arise). It’s a pretty cool feature that stems from browsers using subpixel rendering.
Subpixel rendering, for those of you like me who were not really in the know, is basically a resolution technique where a machine or browser uses the individual RGB sub-pixels within a pixel to help increase resolution of an image or type.
If you were to look under a magnifying glass at a single pixel, you would see that instead of a white dot it comprises three ‘sub’ pixels, one red, one green and one blue (some variations to this setup exist but for the purposes of this article we are focusing on the RGB model). These, when all are active, look to our eyes like a white dot because of the size of the pixel and the limitations of our optic nerves. Cool right? But what am I getting at?
Subpixel rendering affects developers in more than just a font-size or anti-aliasing way.
The following relies heavily on the research reported by Alex Kilgour, which he documented in this article.
Here is a code example similar to what I encountered: If a containing div has a width of 1035px (it can be any random number) and you have set within that container four child divs with a width of 25.2794% (I chose this for illustration purposes, I know you are all thinking you would never put a decimal like that after a percent), the width of each child element would not be a round pixel number once it is computed.
In fact, the first thing that happens is — depending on the browser — that number is first truncated and then calculated. After that, it applies the rounded percentage number to the element width. In this case, what we would expect to see is: 1035 *.252794 = 261.64179px. Unfortunately, that is not what the browser would return.
IE9 would calculate 1035 * 0.25 which would return 258.75px. It would then round to the nearest integer, which is 259px — an almost 3px difference.
In IE10 and IE11, the percentage is truncated the same way, applied to the width and then kept at 258.75px and displayed using subpixel rendering.
Firefox will round the percentage to three decimal places, which would yield 260.82px, then display that value using subpixel rendering. This is closer, but still off.
Chrome rounds to 13 decimal places, applies the width and then displays the resulting value using subpixel rendering.
Safari behaves the same as Chrome, but with 15 decimal places.
Android goes to 15 decimal places, calculates the width, but then rounds down.
As you can see, there is no set method to deal with these fractions of pixels, nor is there any guidance in the spec.
The modern browser takes a look at that calculated pixel width, and seeing that it is a fraction will try to display each child at a calculated width that has been rounded and truncated before rendering. All (modern) browsers support subpixel rendering in some fashion now, and can use that to split the divs equally. In the event the width is pushed beyond two or three decimal points you start to run into a lot of issues. This can rear its ugly head in the form of, say, a calculated column width that doesn’t quite cover the width of the containing div, leaving a 1-2 pixel gap between one or more of the columns depending on the width of the browser.
I started a Codepen in Chrome to document it, and after seeing the variation within just Chrome when I altered the decimal places and percentages, it really shocked me.
“Where are those solutions?” I bet you are asking. Unfortunately, there are none from a standards perspective. This is one of those grey areas where the spec doesn’t specify one way or the other how to successfully handle this, so each browser has on its own created some variation of the truncate-calculate-round steps.
In my project, restructuring exactly how the columns were rendered and creating an artificial border between the columns with a negative margin effectively hid the error, but it was more a hack than an actual solution. Not ideal at all, but it got the job done.
You could also, for the time being, reduce your dependency on percentage for widths. (GASP. I know, right?)
You could also use a hacky column width that uses negative margins to push the element from either the left or the right, but it’s sort of fragile.
This challenge enabled me to learn that this very common occurrence, but it may only show up once in a blue moon in the form of some small, very difficult to diagnose, browser-specific bugs. Here is hoping that a future version of the spec standardizes this for all browsers to implement.
Let us know if the article was helpful, interesting or suggest what you would like to hear about. Email us at firstname.lastname@example.org. Thank you for reading.