I love rounded corners as much as the next guy and CSS3 makes it really easy to use them on the web. Unfortunately, there are some weird quirks and bugs that can spoil the fun. This week, I wanted to style something with to look like a progress bar and those rounded corners really bit me in the ass. Put simply, I wanted to create something that looked roughly like the figure below:
It turned out that it’s not as easy as it looks to style an element to behave like that. The difficult part is the fact that the progress bar is flat, but fits nicely in the rounded corners of the containing shape. As it turns out, there are some bugs in Webkit and thus Chrome and Safari when it comes to the clipping of child elements of elements with rounded corners, that make it hard to get this to look like in the picture. I finally found a creative solution. If you’re just interested in the source, check out the demo page. Otherwise, do read on!
Understanding The Goal
As I explained, I wanted my progress bar to have rounded corners, and dynamically fill up like this:
Furthermore, I wanted this to be flexible. That means no fixed-width images or other “cheats”; CSS only.
First Try: Nested Progress Div
The most obvious solution for this was to build the progress bar from two nested divs: one to represent the whole item and then one to represent the progress.
We give the outer div a width and a fixed height to keep it simple, but with an absolutely positioned inner div, it could just as well have a dynamic content size.
We can now stretch the inner div to the desired length by setting its
width, leading to this:
The result looks great in Firefox, but looks broken in Chrome and Safari, due to the aforementioned bugs. WebKit does not properly clip the content to the parent’s rounded outline, so it ends up looking rather awkward.
Second Try: Working Around the Bugs
Although the solution with the nested div looks exactly as intended in Firefox, it doesn’t really hold well on WebKit due to the lack of proper clipping. Perhaps something can be done to work around the bugs and make it look good anyway.
One workaround would be to give a rounded corner to the inner “progress” div as well. This removes the clipping artefacts on the left, but results in an unwanted rounded corner on the right of the progress bar. In itself not an unappealing sight, but not what we intended:
Furthermore, the rounded corners on the left and right get pushed together when the width of the bar is less than the desired size of the corners, resulting in a squeezed shape that bleeds past the nice rounded outlines:
Dropping the rounded corner on the right hand side makes it look good, but only when the bar is more than X pixels wide and more than X pixels away from the right corner (otherwise it would still clip there). By now, it’s obvious that this is a dead end. Clearly we need a workaround that does have proper clipping. As it happens, such a workaround exists.
Final Solution: Background Clipping
While reading about clipping nested elements with rounded corners, there was one solution that kept popping up. A common problem is to have an image with a caption wrapped inside a div with rounded corners. This is then solved by removing the img-tag and setting the image as a background to the rounded div instead. It’s an old solution, that can even be done automatically using jQuery. It works, because even when rounded corners do not clip the contents of an element, they do affect the background. This turned out to be the key to the solution.
Instead of using a nested div to represent the progress, we use the background of the div itself to represent progress. We give it a
background-color and a
background-image and scale them accordingly to show progress. This way, we no longer need the inner div anymore:
And since we just want a uniform fill on the background, we can use a repeating 1-pixel GIF for the background and stretch it using the
This results in a progress bar that looks exactly as intended and fills up the rounded corners perfectly without artefacts, all the way from 0% and 100%:
There we have it! This works in all browsers that support rounded corners and degrades nicely in those that don’t (I’m looking at you, IE 7 and below).
There are some caveats. First of all, due to the way image backgrounds work, a background-width of 0% still results in a 1px wide red bar. Therefore, the image background actually starts 1px to the left and goes to 101% instead of 100%. More importantly, we are now using an image to style the background. This has two big drawbacks: firstly, the image requires an extra request to the server to be made, slowing down the loading of the page. Secondly, an image is arguably less flexible than a purely declarative CSS solution.
The extra request is easily solved: rather than linking to the (tiny) image, we embed it directly in the CSS using a data-URI. It’s only 43 bytes large, so it fits easily:
Note that this does not work on IE7 or below, so be sure to add a fallback to support those.
The flexibility part can actually also be solved this way, but that is something for another time.
That’s all! Using an overlapping background-color and background-image, we can make a sharp-looking rounded progress bar and avoid those maddening clipping bugs. Of course the real solution lies in a cross-browser method to apply rounded corner clipping to child elements, but until then this gets the job done.
For the full source and the ability to see the progress bars animate live, check out the demo page.