CSS background-position and percent

I was working on making an existing client site responsive recently and encountered a CSS problem I am unable to find a good cross-browser solution to. The problem involves CSS background images and seemed simple at first, but it turned out otherwise.

The site I was working on was using a fixed width. One column had a coloured background extending all the way from the header to the footer, regardless of the amount of content. To achieve this, a background image was used (à la Faux Columns). I needed to make this flexible as part of the responsive remake.

Making the width flexible and changing the column widths from pixels to percentages has an interesting consequence when you use a background image to fake columns because of the way the background-position CSS property works when you use percent, i.e. background-position:40% 0.

You may think that the percentage value tells the browser where in an element to put the left and top edges of the image. Not so. The value you specify is applied to both the element and the image. From the CSS 2.1 specification:

A percentage X aligns the point X% across (for horizontal) or down (for vertical) the image with the point X% across (for horizontal) or down (for vertical) the element’s padding box.

The problem with percentages

I’ve found that in practice the specified behaviour is rarely what you really want when using perentages for background-position. In my experience it’s more common to want to align a point X% across an image with a point Y% across the element, where X and Y are different. It can be worked around by adding a transparent offset to the background image so that its total width matches the width of the element it is applied to. This way the point X% across the image will scale proportionally with the point X% across the box. A bit of a kludge, but doable. So I did that in my responsive retrofit.

But then at a certain viewport width I wanted to change the width ratio between the columns. Doing that makes X% of the image width no longer calculate to the same number of pixels as X% of the element width. For a given width of the container box it’s possible to find a percentage value that makes the points match, but as soon as the viewport is resized the image will be misaligned again.

To see what I mean, take a look at the CSS background-position and percent demo document. It contains two elements that each has two floated children to create columns. To create the illusion of the columns being of equal height a background image is used.

If you start off with a browser window wider than 1000 pixels and make it narrower, the background image’s columns (the left column is dark green with a light green right border and the right column is white) align with the widths of the floated elements. But at 800px, 600px and 500px there are media query breakpoints at which the ratio of the column widths changes. This causes the background image to be misaligned in the first set of columns.

There is a sort of brute force solution to this problem—load a different image when the column width ratio changes. So I could do that. But what if I wanted to change the ratio again? Create another image, add another file for the browser to download. I wanted to avoid that, if possible.

calc() to the rescue (eventually)

After a lot of fiddling around with calc() I managed to figure out how to calculate background-position in a way that lets you use % to correctly position an image where the column widths do not have the same proportions as the columns in the box.

On my demo page the background image is 1000px wide and has two columns, one 400px (green) and the other 600px (white). If the CSS columns have the same proportions (40% and 60%) you can use background-position:40% 0. But if the CSS columns are 25% and 75% wide, that won’t work. But this will:

#container {
	background-image:url(bg.png);
	background-repeat:repeat-y;
	background-position:calc(25% - (400px - 1000px * 0.25)) 0;
	/* [col 1 width]% - ([image col 1 width]px - [image width]px * [col-1 width as decimal fraction of element width]) */
}

If the proportions of the columns change to, say, 67% and 33%, you would change 25% to 67% and 0.25 to 0.67. As far as I can tell this works, but please let me know if you spot an error in my math.

So Can I use calc() as CSS unit value? Support is actually a little bit better than I thought (IE9+, Firefox 4+, Chrome 19+, Safari 6+), especially if you’re going to use it inside a media query since that already excludes IE8-. Notable browsers that currently lack support are Safari 5- for OS X and iOS, Opera (Desktop, Mobile and Mini), and the Android browser. So if it’s critical that the background image is positioned correctly in those browsers you will need to load a different image or find another workaround for now.

A background-origo property would be nice

Regardless of calc() seemingly being able to solve this problem, what I’d like to see is something more intuitive—the possibility to specify which point in an image that should match the positioning point in the element. My coworker Anton (@nat0n) suggested a property called background-origo that would let you do this:

.container {
	background-position:25% 0;
	background-origo:400px 0;
}

This would make the point 400px across the image always line up with the point 25% across the box. The default value for background-origo would be identical to whatever background-position is to make the current behaviour the default.

In case this has been discussed and rejected already it would be interesting to know why. For all I know there may be valid technical reasons, or maybe we’re simply expected to use calc() to solve problems like this one.

Posted on February 4, 2013 in CSS