Thursday, October 01, 2009

A quick fix for the 1px rounding error

[Here's the quick solution if you can't be bothered to read this whole post: stick a border on the div that's causing the 1px rounding error].

Now let me elaborate, because there's actually a bit more to it than that...

We (like many other web design companies) generally design our websites as fixed-width, centered in the browser window, and optimised to 1024px wide with no horizontal scrolling at 1024. When you take the vertical scrollbar into account that means the maximum width for the outer container div is 1004px.

Sue likes to design websites with quite substantial dropshadows on either side of the outer container, which makes the whole graphic wider than 1004px. No problem - I just create the dropshadow as a single graphic which I style as a background image on the body tag in the CSS, centered in the browser window by setting the width of the graphic to 50% - like this:

body {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 62.5%;
text-align: center;
margin: 0;
padding: 0;
color: #595959;
background: #009ec7 url(../img/bg-page.jpg) no-repeat 50% 0;
}

#container {
width: 960px;
margin: 0 auto 0 auto;
padding: 0 0 10px 0;
text-align: left;
background-color: #fff;
}

The example above is for a site where the shadow starts off fairly narrow at the top, gets a lot fatter for a couple of hundred pixels, then gradually gets narrower again until it disappears altogether. It makes the page look like it's rising off the background for a while near the top of the browser window.

Here's the shadow graphic (click to enlarge):



With a design where the shadow is a consistent width all the way down the page, you would take a narrow strip of shadow and save that as a vertically-tiled background image instead - and the CSS would look like this:

background: #009ec7 url(../img/bg-page.jpg) repeat-y 50% 0;

The problem with this solution (whether you use a single or a tiled background image) is the mixing of the fixed-width #container div together with a percentage-positioned background graphic. Exact pixel widths don't play well with percentages in CSS, and the combination of the two is likely to create a 1px rounding error in many situations. It's why we generally stick to one or the other and don't use both together unless we absolutely have to.

In my example, this is what you get when you view the site in Opera or Safari on my Mac (click to enlarge image).

This is a screenshot of the right-hand side of the page within the browser window. You can clearly see a 1px-wide white "border" to the right of the light blue header, and you can also see a distinct "step" further down the page, which marks the bottom of the dropshadow background image on the body tag.

Interestingly enough, the 1px rounding error on this site only showed up in Safari on my Mac and Opera on Mac and PC. Firefox was fine (of course) and amazingly enough, IE6 and IE7 on my test PC also handled it just fine - not a rounding error in sight!

But that's not good enough. My sites get tested in IE6, IE7, IE8, Firefox, Opera and Safari (at a minimum), and if it's not perfect in all browsers on all platforms I'm going to do my damnedest to try and fix it.

The 1px rounding error is one of the few bugs that gurus such as Holly and Big John at Position is Everything have reluctantly assigned to the "annoying bugs that can't be fixed" basket, but because I initially didn't recognise my bug as a 1px rounding error, I tried to fix it myself.

...and found a workaround for this particular example that works pretty well...

It took me ages to figure out that the "step" was actually the bottom of the background graphic in the body tag. It didn't seem to match up with any of my divs on the actual page when I tested it with Firebug, and for a while there I was completely stumped. What's causing this step???? Arrrrgggghhhh.

I tried a bunch of things to get rid of that nasty little white line to the right of the light blue header. Maybe the white is showing through from the white background on the #container div that's wrapped around the header div? Could I add a light blue background to the top of #container so the line is light blue (matching the header) instead of white? Nope. Doesn't work. And even if I had been able to fix the header issue using that kind of solution, the step would still have been visible lower down the page.

Think again.

The fact that the white line appeared to be behaving more like a 1px-wide border than a background showing through a 1px-wide gap (because I couldn't fix it by adding a coloured background to #container) made me wonder what would happen if I tried adding a border of my own to #container instead.

Here's my first test - a variation on the classic "border: 1px solid red" that I use whenever I'm bug-fixing to identify the div I'm working on (click image to enlarge).

This example uses "border-right: 1px solid red;" on the #container div.

Awesome! The step is gone! How crazy is that?

Now we have a consistent red border all the way down the right-hand side of the #container div, instead of the bug-created white border-like line that only goes down as far as the bottom of the page background graphic.

From this point, it's easy to see how to make the border almost disappear - to a point where I can live with it as a "quick fix", anyway...

By selecting the hex value of the light blue header background at its farthest-right point (which in this case is #b2e2ee) and applying it to #container div instead of the red border, like this:

#container {
width: 960px;
margin: 0 auto 0 auto;
padding: 0 0 10px 0;
text-align: left;
background-color: #fff;
border-right: 1px solid #b2e2ee; /* fix for 1px rounding error bug */
}

...I get this result (click image to enlarge).

Now the right border blends seamlessly into the light blue header to its left at the top of the page, and further down the page it blends nicely into the darker blue page background to its right.

It's a bit of an optical illusion, because if you look very very closely where the light blue header meets the white content area of the page, you can see the light blue bleeding into the white, but hey - I can live with that. It's much better than a white border and a white step, anyway.

Wait, I can't live with it! That bit of bleeding blue's annoying me. Yes I know I've already delivered this particular website to the client, but I can always get them to tweak the stylesheet for me...

Here's an even better colour choice for the border:

Grab a representative darker shade of blue page background from your photoshop file. I've used #00a1ce. Use that instead. This what you get (click image to enlarge).

OK, this optical illusion is better than the last one. No bleeding blue, because we're not using a colour that appears in part of the actual page (the light blue) and then stops (where the white content area begins).

By choosing a colour that matches the page background, rather than part of the page itself, the border blends outwards rather than inwards, and the edge of the page appears to be completely uniform. If you have a shaded background like in this design, just pick any pixel from close-in to where the background meets the page, and you should be fine with whatever shade of blue you end up with. It's an optical illusion, after all.

To achieve the neatest possible result, it's probably a good idea to replicate your border on both sides of the #container div:

#container {
width: 960px;
margin: 0 auto 0 auto;
padding: 0 0 10px 0;
text-align: left;
background-color: #fff;
border-right: 1px solid #00a1ce; /* fix for 1px rounding error bug */
border-left: 1px solid #00a1ce; /* fix for 1px rounding error bug */
}

In Sue's design for this particular site, the header starts off white on the far left-hand side and gradually shades to light blue as you move right. My original light-blue border fix wouldn't work on the left of the container, as it contrasted with the white header on that side, but having the blue match the darker blue page background works fine, because the background is consistent on both sides.

This solution can also be adapted for a design where the background color changes dramatically as you progess down the page. A background colour that fades vertically into white or another colour entirely, for example. Or a drop shadow at the top of a white background that fades away as you scroll down the page until it eventually disappears into the white.

All you need to do is experiment with the colour of the border until you find one that both you and the designer can live with. It might be white, giving you a 1px white frame on both sides of the page, between the container and the dropshadow. It could be a shade of grey, as long as your designer can live with their fading dropshadow eventually becoming a 1px grey border instead of disappearing completely into white. Whatever works for your particular design.

Yes, I know this means you may have to go back and get the designer to tweak their design because you can't make it work (which is pretty much always a no-no as far as I'm concerned), but we are dealing with the dreaded 1px rounding error here, and even Holly 'n' John haven't found a complete fix for it...

Yes, it's a workaround rather than a complete fix - in that it doesn't remove the 1px line, it simply overwrites it and then disguises it - but I reckon it's a good solution to the particular visual problem we had in our design - and one which I think might be useful to other designers and developers out there.

It even provides a fix for Big John's Rounding error test page, without breaking the layout - even in IE6 - which is rather amusing, because if anything's guaranteed to break an IE6 layout it's adding a border to a percentage div where all your combined percentages already add up to 100%...

Change John's style from

#wrapper div {
position: absolute;
background: black;
width: 20%;
height: 20%;
}

to

#wrapper div {
position: absolute;
background: black;
width: 20%;
height: 20%;
border-right: 1px solid black;
border-bottom: 1px solid black;
}

...and you'll see what I mean.

The reason why the 1px border fix doesn't break John's example layout, even in IE6, is because the divs have space in which to spread out - they're free to sit nicely next to each other because they're absolutely positioned which takes them out of the flow - rather than being floated. In John's example, if I float the divs by changing his styling to:

#wrapper div {
float: left;
background: black;
width: 20%;
height: 20%;
border-right: 1px solid red;
border-bottom: 1px solid red;
}

...the placement of the divs breaks and the last div ends up in a new row (I've coloured the borders red so you can see 'em - click image to enlarge). This is because the addition of the 1px right borders means that the total width of all the divs in a row is now more than 100% - and with floated divs this matters. It breaks in all browsers, including compliant ones like Firefox.

John's original example (without my borders) also breaks in IE6 and IE7 if you float the divs instead of absolutely positioning them (although it looks fine in Firefox). This is because the 1px rounding error also adds additional width to the divs, making them too wide for their container. In the case of IE6 and I7 this results in a horrible break/not break bounce to the page as you slowly increase or decrease the browser window width and as the rounding error comes and goes. This is presumably why he decided to build his example as a series of absolutely positioned divs instead of floated ones :)

The reason why my 1px border solution doesn't break our website in IE6 and IE7 is because of the way in which the website is built. The #container div is the outermost div on my page, and although it isn't absolutely positioned (or floated, for that matter) there are no other divs wrapped around it to constrain its size in any way. This means that adding a 1px border on either side (so increasing its overall width by 2px) isn't going to break anything by making it too wide. It just spreads out a bit, ending up 2px wider than the original page design.

The only time this would be an issue is if your design is already 1004px wide, which is the maximum it can be to avoid a horizontal scrollbar at 1024 on pages that have a vertical scrollbar. In this instance you would have to decrease your page width by 2px in order to stay within the maximum once you add the 1px border on either side.

So - to summarise:

If your div structure will allow you to do this without breaking it, a fix for the 1px rounding error is to add a 1px right border to the div where the rounding error is occurring, and colour it to match whatever colour you've got wrapping around that div. The border will overwrite the rounding error and the colour will camouflage it to match its wrapper so that it becomes virtually invisible.

By careful selection of your border colour, and by tweaking any wrapper div widths to allow for the width of the border without breaking the layout, this solution will work with a range of layouts, providing you with a controllable border instead of an uncontrollable 1px error which, in older browsers like IE6 and IE7, comes and goes as you increase and decrease the browser window width.

I was pretty thrilled to stumble upon this solution, and I hope it's of use to someone out there. I know it's not the perfect answer in all situations, but hopefully it's flexible enough to help in a variety of layouts - and in a wide range of browsers. It works in IE6, IE7, IE8, Firefox, Safari and Opera. I haven't tested it in earlier IEs or any other browsers - let me know what happens if you do.

Let me know how you get on!


Technorati tags: , , , , , , , , , , , , , , , , , , , , .

Read the full post