When working on front end development projects, we’re finding that a great many more sites are taking advantage of small JavaScript animation effects to make interactions feel more polished. One thing you may notice if you use Firefox 2, particularly on a Mac, is that as you animate the opacity of an object the text on the whole of the page can appear to dim slightly. This, in fact, happens with any change of opacity on the page.
The effect is particularly noticeable when the page’s colour scheme uses light text on a dark background. What’s happening is that Firefox is switching from using the operating system’s text anti-aliasing to using its own internal system. As you can imagine, when you switch from one system of anti-aliasing to a completely different system this can have a noticeable impact on the appearance of any type.
This clearly isn’t a new problem, and the anti-aliasing switch is no longer apparent in Firefox 3. A common way of addressing the issue is to force Firefox to use its own anti-aliasing from the outset by setting the opacity on the entire page to something very close to the maximum value of 1. Typically 0.9999 or similar. This has no visible impact other than to kick Firefox into a text rendering mode that it can stick with throughout the animations.
I often use jQuery for quick, simple UI animation jobs, and so I would often do something like this:
$('body').css('opacity', 0.9999);
This nicely prevents the text dimming effect in Firefox 2. So job’s a gooden, right?
Enter stage left: Internet Explorer 7
One of the other techniques we’re seeing a big uptake in is the use of alpha-transparent PNG images, thanks to native support in IE7. Up until now, developers have had to make use of an awkward filter for PNG transparency in IE6. With healthy adoption rates for IE7, creative use of PNG transparency is definitely on the up.
There is, of course, a snag. IE7 appears to have a bug whereby changing opacity on an image element containing an alpha-transparent PNG causes the transparent sections of the image to turn grey (just like the default IE6 behaviour). You can view this demonstration page in IE7 to see it for yourself. Early betas of IE8 have addressed this, and to be honest you can see why IE7 might flip out a bit if you take an image with variable transparency and then try and adjust the opacity of the entire thing. I think I’d flip out too. But I digress.
The issue here is that if you’ve used something like the code above for setting the opacity for the entire page to something less that 1, any transparent PNGs in the page are going to turn grey in IE7. The solution, of course, is to be more specific in your targeting. As jQuery has some basic built-in browser sniffing, I found it easiest to modify my statement to:
if (!$.browser.msie) $('body').css('opacity', 0.9999);
This would apply the opacity change to everything except IE, which in this instance works just fine as IE is the only browser that appears to have a problem with it. There may well be a better way to target FF2 specifically (update: see some great suggestions in the comments below). If you’re using an IE specific stylesheet with conditional comments, you could correct the opacity change in there equally well.
As so often is the case with cross-browser and cross-platform development, fixing one issue can lead to another. Our job as web developers certainly isn’t easy.



Comments
This fix works like a dream! thanks for writing it up Drew. I have noticed this strange rendering behavior in firefox two even when I am not animating the opacity, on occasion when I am only doing very minor changes in Javascript on the page, this fix works then too. I have it set now on the body and over-ridden as opacity:1; for IE.
Well you do have -moz-opacity for all it’s worth
Shaun Inman has an interesting tweak for Safari, addressing a similar rendering issue (high-contrast fonts, it seems?) – basically applying an invisible text shadow fixes things there.
http://24ways.org/2006/knockout-type
This sounds like the same approach/result (OS anti-aliasing vs. browser aliasing?) as with Firefox, but I’m not certain that opacity:0.99 for example would produce the exact same result on Safari.
I’ve noticed Firefox 3 has made the font rendering more approximate to Safari’s behaviour as well, the fonts seem to be “fatter” than previously.
I don’t have any hard data, but I’m a bit wary of applying these effects on large production sites as it may have performance implications (for example to render the whole document at 99% opacity.) I am a fan of nicely-rendered text though, so don’t get me wrong – I use the shadow technique on my own site for Safari, and the difference in rendering is highly noticeable. Sometimes I use “font-weight:lighter” which seems to work quite nicely in Firefox.
I rather prefer the non-native text aliasing in the “after animation” image, is that wrong? ;)
Alternatively, since the proprietary renderer-specific extensions are still supported by both Safari and Firefox, you could do this in your reset:
body {
-moz-opacity: 0.999999;
-webkit-opacity: 0.999999;
}
These are the old ways of doing opacity, before both browsers started actually supporting it without the
prefix. They’ve been left in for compatibility reasons. This way you fix the rendering only for the browsers that need it, and with no actual “hacks”.Great suggestions, all.
Jonno Riekwel just wrote about about the details of this issue in Safari.
It looks like the css opacity fix that Adrienne suggested is the answer.
Drew: This will target Firefox, version 2.0 and lower, and only on OS X…
if ($.browser.mozilla && parseInt($.browser.version, 10) <= 2 && navigator.appVersion.indexOf(‘Mac’) !== -1) {$(‘body’).css(‘opacity’, 0.9999);}
In relation to Firefox 2+ on Mac however, using css opacity vallue less than 1 essentially kills Flash, making it act “peek a boo”-like.
We’ve been experiencing similar problems in one project and tried to fix it with $(‘body’).css(‘opacity’, 0.9999); too. What we noticed was that Opera had severe problems with that (can’t quite remember what it was), which made us prefix the above code with if ($.browser.mozilla) …
To add another quirk to the mix, Firefox 3 now appears to introduce yet another anti-aliasing method when opacity is set. It doesn’t apply to all elements on the page the way Firefox 2 did, but it’s definitely an undesirable effect. It looks like it’s just making the anti-aliasing too strong and the edges get extra chunky.
Safari 3 actually exhibits similar behavior when using opacity without the text-shadow trick, but the method it reverts to, while definitely too strong (and chunky) uses sub-pixel anti-aliasing (colored pixels) if your system-wide settings are set to LCD. The Firefox 3 glitch appears to use monochrome anti-aliasing no matter what, but it’s just too strong.
Now if we can just figure out a way to trick Firefox 3 into using something closer to what FF2 and Safari are using with the opacity and text-shadow tricks, we’ll be set. Unless Safari 4 “fixes” the text-shadow switch, then I’m quitting the Internet.
Here’s a screenshot of the Firefox 3 glitch in action. It only crops up when you’re using one of the LCD-optimized system-wide settings, so I suspect it’s a conflict between the anti-aliasing applied by the Cairo graphics engine and the system.
Nathan: Actually, jQuery’s browser.version returns the Gecko version for Firefox, not the Firefox version (which is weird). Firefox 3 is 1.9, and Firefox 2 is some variant of 1.8. So you have to use parseFloat to get the second digit as well:
if ($.browser.mozilla && parseFloat($.browser.version) < 1.9 && navigator.appVersion.indexOf(‘Mac’) !== -1) {$(‘body’).css(‘opacity’, 0.9999);}