12 Replies - 12490 Views - Last Post: 13 July 2012 - 08:45 AM

Using PHP to deliver conditional CSS

Posted 10 July 2012 - 03:05 PM

I'm currently at a crossroads in my project. It involves CSS. And it involves the arch-demon of the web. Yep, you guessed it, IE. But rather than get bogged down with a particular problem, let's broaden the discussion to the concept of delivering CSS conditionally, based on the browser as PHP determines it.

I'll state firstly that this wouldn't be a topic worthy of discussion in the PHP forum if it wasn't for IE. Why? Because IE has a habit of doing the wrong thing when interpreting CSS. Take the following for example:

Try that in Firefox, Opera, Chrome and/or Safari. Now try it in IE. Notice how all the standards compliant browsers show transparent fading to red, and IE shows yellow fading to red? That' because IE takes note of both the background attribute and the filter attribute. That plays havoc with the site that is trying to be cross-browser. We need to deliver the CSS conditionally.

Now, there is a function in PHP called get_browser() that can determine browser settings of a client. It gives output similar to below:

Great information, and something easy to work with. There are drawbacks to using this function though. An unlikely drawback is that a client can spoof their browser settings. A more likely drawback is that the browser settings are blocked by the client's firewall. As far as I know, both of these are definite possibilities that need to be considered.

A further consideration is the options we have available to deliver conditional CSS. I can envisage 3 possibilities:

1. Conditionally include files
Taking the example of IE requiring substantially different CSS, then we would require 2 different files.
Pros

Easy to write the conditional PHP
Simplest solution

Cons

We need to maintain multiple CSS files
If another browser comes along that requires special conditions, then we need to create another file

2. Conditionally comment out certain lines using preg_replace_callback
So we could also do something like this in CSS:

<%IE%>background: #ff0000;<%/IE%>

...and change those tags to be either empty strings, or /* */ strings depending on whether the browser is IE for instance.
Pros

Requires only one file

Cons

Moves the onus to the server, making it a code-maintenance issue
If another browser comes along that requires special conditions, this complicates the code

3. Template the CSS, using tags and preg_replace_callback to deliver the CSS
So envisage a CSS file that has content like this:

Easily extensible
Easily maintainable
Centralises any changes you need to make
Allows the coder to wrap complex PHP-driven conditionals around the CSS

Cons

Difficult to keep up to date with constant changes in browsers
Obfuscates the CSS template
Difficult to track bugs in your CSS

Personally, I like option #3, since it gives me ultimate control over how the CSS is delivered, and also allows complex logic. The prospect of having to keep up-to-date with browser prefixes is a very potential major pain though.

Replies To: Using PHP to deliver conditional CSS

Re: Using PHP to deliver conditional CSS

Posted 10 July 2012 - 03:58 PM

To be honest, I wouldn't consider using PHP to solve this issue at all.

The first thing that comes to mind when considering your solutions is that, for at least the last two, you lose the ability to properly cache CSS files if they are being altered on a browser-specific basis on the server. The first suggestion, if done in a certain way, could bypass this, but in that case it would be no more than a clone of it's current client-side counterpart, so you may as well not bother.

The second thing is, as you mentioned, the unreliability of browser-sniffing. Especially on the server-side. You could never be 100% sure that the user-agent string you are processing is in fact valid, or if it will be sent at all. Any system that depends on it would never be as effective as client-side conditional comments.

The only major browser that ever really requires CSS fixes -- at least fixes that can't be accomplished in a single CSS file -- is IE. Luckily Microsoft saw fit to recognize this and provide us with the Conditional comments to deal with the problem. I've never seen any reason to avoid them in favor of custom server-side functionality that does pretty much the same thing.

That said, out of your three methods, I would probably go for #3. Except that I would try not to use placeholders. I'd just write the standard CSS styles and search through the text for lines to replace, using the standard style names as keywords.

Re: Using PHP to deliver conditional CSS

Posted 10 July 2012 - 04:30 PM

Atli, on 11 July 2012 - 09:58 AM, said:

To be honest, I wouldn't consider using PHP to solve this issue at all.

The first thing that comes to mind when considering your solutions is that, for at least the last two, you lose the ability to properly cache CSS files if they are being altered on a browser-specific basis on the server. The first suggestion, if done in a certain way, could bypass this, but in that case it would be no more than a clone of it's current client-side counterpart, so you may as well not bother.

This is true, and a con that I hadn't thought of. In my particular case, it's not an issue, as the CSS file is templated anyhow, due to the fact that users can create their own skins for the site. But certainly, in terms of a wide solution to the problem of (primarily) IE, that is a consideration to take.

Quote

The second thing is, as you mentioned, the unreliability of browser-sniffing. Especially on the server-side. You could never be 100% sure that the user-agent string you are processing is in fact valid, or if it will be sent at all. Any system that depends on it would never be as effective as client-side conditional comments.

Yep, true, so you'd need to have a fallback option in the case of unknown browsers. Unfortunately though, that fallback option would have to be the verbose set:

...in order to cover your bases. If you don't know what the browser is, you can't make any assumptions about it, and falling back to just the standards-compliant attribute won't cut it in most cases of CSS3, where there are very few truly standards compliant browsers even still.

Quote

The only major browser that ever really requires CSS fixes -- at least fixes that can't be accomplished in a single CSS file -- is IE. Luckily Microsoft saw fit to recognize this and provide us with the Conditional comments to deal with the problem. I've never seen any reason to avoid them in favor of custom server-side functionality that does pretty much the same thing.

The biggest reason to avoid that is that it applies to HTML only, not CSS. Which is painful. The one place you really need it is CSS. Which brings us back to the problem of conditional CSS

Quote

That said, out of your three methods, I would probably go for #3. Except that I would try not to use placeholders. I'd just write the standard CSS styles and search through the text for lines to replace, using the standard style names as keywords.

I thought of that as an option as well (using standard names), but I felt that that masked the fact that the style was being generated by code. I like seamless integration, but I've had my fair share of discovering hidden "gems" in code before, and I like to bring a fellow coder's attention to the fact that what I'm doing isn't by-the-book.

Another reason I thought using non-standard names in code tags is that you can fish out the tag content, and utilise class methods. I haven't got it on me, but I'm pretty sure the following code achieves this:

If you use standard names in your css, then you'll need to throw in a switch() in the call to LambdaReplacer. Not a big deal, I know, but it is something.

I guess the biggest concern I'd have for using the standard names, though, is that sometimes you have special conditions, like in the example I provided in the OP. In that case, you use background for compliant browsers, but filter for IE. Considering that there are different hexstring / ARGB formats between the two, and different effects from having a "fallback" for background, we need intricate logic anyhow.

Re: Using PHP to deliver conditional CSS

Posted 10 July 2012 - 10:36 PM

e_i_pi, on 11 July 2012 - 01:30 AM, said:

Quote

The only major browser that ever really requires CSS fixes -- at least fixes that can't be accomplished in a single CSS file -- is IE. Luckily Microsoft saw fit to recognize this and provide us with the Conditional comments to deal with the problem. I've never seen any reason to avoid them in favor of custom server-side functionality that does pretty much the same thing.

The biggest reason to avoid that is that it applies to HTML only, not CSS. Which is painful. The one place you really need it is CSS. Which brings us back to the problem of conditional CSS

I might note that, unless you use CSS in a non-HTML context, which obviously excludes the use of browsers, you call the CSS from within HTML.

Re: Using PHP to deliver conditional CSS

Posted 11 July 2012 - 01:15 AM

Crap on a stick, I'm sure I used that format for commenting and it didn't work before in IE. Now it does. Urgh, why did I waste so much brain power thinking of solutions to this problem that doesn't exist.

Re: Using PHP to deliver conditional CSS

That' because IE takes note of both the background attribute and the filter attribute. That plays havoc with the site that is trying to be cross-browser.

Not to defend IE, but this is a completely bogus example. The problem here is simply a misunderstanding of how CSS backgrounds work.

The issue is that "background" is a shortcut rule. Filters in IE are a separate property from backround, so setting a filter will not override a previously set background color. When you set background: #ffff00, you're really setting background-color and implicitly setting background-image to none. Conversely, when you set the gradient via "background", you're really setting background-image with an implicit background-color of none. When you use filter, these implicit "nones" don't get set, because there's no override - it's a totally different property. In fact, if you do it this way, you'll find that other other browsers actually display the same thing as IE (at least, WebKit does - I'm too lazy to test in the others):

Actually, no, it's not. When I tested that, the styles inside that comment weren't applied in IE, but they aren't applied in any other browser either. So that doesn't really do the trick.

Honestly, I think you're making this more complicated than it needs to be. Unless you're stuck supporting very old versions of IE, the rendering differences aren't usually big enough to justify massive server-side processing. There are plenty of simple ways to do apply IE-only styles client-side. Personally, I find that just sticking to the well-known IE-specific CSS hacks is usually sufficient. This also has the benefit of keeping the IE-specific stuff right next to the "normal" styles, so that I don't have to remember to go check a different file for IE overrides every time I make a CSS change. And for the rare occasions where I can't properly target a known hack, I'll use jQuery to do the browser-sniffing and set some class to make the override easier. So, for example, on page load I might do something like:

RudiVisser has thrown up the red flag on this by the looks, suggesting (I think) using it in the HTML rather than the CSS. The problem I have with this is that it involves maintaining two different CSS files with largely the same content. This smells very bad to me, as bad as putting in those IE hack conditionals in the CSS file, and if it comes down to one bad thing vs another, I'll choose tha bad thing that involves less work when it comes to fixing it in the future.

Quote

Honestly, I think you're making this more complicated than it needs to be.

I wish I was, perhaps a screenshot of FF13 vs IE9 will convince you otherwise:
Here's the list of issues with IE, for that screenshot of about 1/8th of a single page on the site:

This isn't a very old version of IE. This is IE. It's a plain terrible browser. I've run tests on the server-side processing times, and they're very light (maybe 10ms to code-generate CSS).

Quote

There are plenty of simple ways to do apply IE-only styles client-side. Personally, I find that just sticking to the well-known IE-specific CSS hacks is usually sufficient. This also has the benefit of keeping the IE-specific stuff right next to the "normal" styles, so that I don't have to remember to go check a different file for IE overrides every time I make a CSS change. And for the rare occasions where I can't properly target a known hack, I'll use jQuery to do the browser-sniffing and set some class to make the override easier. So, for example, on page load I might do something like:

The problem I have with this (and it may be a small problem I should get over) is that it moves the onus of styling away from the stylesheet, and into multiple files in (6 months down the track) places I won't remember. I do in fact do some styling in my JS files, but very little. Most of it is inline when I have no other choice, for example when I'm retrieving an unknown hexstring colour from a data source such as a DB query.

I guess the reason I posted this topic in PHP Advanced rather than PHP is because it's not a question about a specific issue I'm having, but an issue that all web developers face, and whether or not approaching it from a server-side perspective has merits.

You are only half right there, actually. The "NOT IE" syntax is a little different from the "IS IE" syntax. Basically, this is how you'd use the syntax to apply browser specific CSS within a HTML file:

The one that is used in browsers that are not IE has to be different, because other browsers don't parse these commends like IE does. It has be set up so that the comments don't actually block the styles, leaving it up to IE to remove if the condition tells it to. Other browsers just show the styles; there is no reason for them not to. The one used for IE, however, needs to do the exact opposite. It needs to block the styles naturally for non-IE browsers, leaving it up to IE to show them if the condition is right.

Note the ; behind the "IF IE" one. I found that if left out, the first CSS style in that condition will be left out. (Odd, but seems to work.)

Re: Using PHP to deliver conditional CSS

Posted 12 July 2012 - 02:07 AM

e_i_pi, on 11 July 2012 - 11:08 PM, said:

RudiVisser has thrown up the red flag on this by the looks, suggesting (I think) using it in the HTML rather than the CSS. The problem I have with this is that it involves maintaining two different CSS files with largely the same content. This smells very bad to me, as bad as putting in those IE hack conditionals in the CSS file, and if it comes down to one bad thing vs another, I'll choose tha bad thing that involves less work when it comes to fixing it in the future.

Your second IE-specific CSS file would only contain the overrides that you need, ie. resetting background to be empty (in this specific example).

This is a very very common practice to fix IE-specific bugs and can be seen in every website on the web (hint: hit Ctrl-U now).

Quote

This isn't a very old version of IE. This is IE. It's a plain terrible browser. I've run tests on the server-side processing times, and they're very light (maybe 10ms to code-generate CSS).

I would disagree with this, it's not a plain terrible browser and in fact the later versions of IE had less quirks than Firefox versions at the same time (anybody remember Firefox's "wonderful" float: right bug?!). The most recent versions of IE (9/10) are brilliant in most ways and I almost use it (10) as my primary browser next to Opera.

Anyway, with regards to the tests being light, yes of course, but it's yet another PHP process to be forked depending on how your server is setup, and under load that's 1*[request-count] extra, 100 visitors would no longer spawn 100 PHP processes but 200, and even though it would normally complete quickly, under such load they would take time and could eventually lead to resource starvation. Of course, it's a very farfetched example, but you see my point, it's not always going to perform the same and could be scalability issue.

What you can do client side, you should do. Including an IE specific stylesheet is not a burden and is completely transparent as you would literally just be overriding the few things that cause problems, not maintaining an entire stylesheet just for IE.

Re: Using PHP to deliver conditional CSS

Posted 13 July 2012 - 08:45 AM

e_i_pi, on 11 July 2012 - 06:08 PM, said:

I wish I was, perhaps a screenshot of FF13 vs IE9 will convince you otherwise:

You know what? In the grand scheme of things, that screenshot really isn't so bad. I don't see anything there that would actually stop you from using the page. Granted, there are a lot of issues in your list, but none of them look terribly difficult to fix. You could probably knock all those out in a day or less. Heck, most of them aren't even "must-fix" issues - just letting IE not have rounded corners and background gradients is perfectly fine in many cases.

Quote

I guess the reason I posted this topic in PHP Advanced rather than PHP is because it's not a question about a specific issue I'm having, but an issue that all web developers face, and whether or not approaching it from a server-side perspective has merits.

That's a valid question. And no, I don't think there's much merit to server-side approaches. Something like the template approach you described is fine, and it definitely has some value, but not in the area of browser compatibility. The problem is that it only solves the easy issues - like the amount of boilerplate code you have to write for things like background gradients. And while that's fine, it's more of a nice-to-have than a compelling reason to adopt a new approach. Templating can't address the real productivity sink of browser differences these days, which comes in the interactions between different rules and different elements. To really do that, you need something that has knowledge and control of both the styles and markup for page elements and can abstract away the differences. So, for example, something like ASP.NET server controls - which are not without issues of their own.

RudiVisser, on 12 July 2012 - 05:07 AM, said:

Of course, it's a very farfetched example, but you see my point, it's not always going to perform the same and could be scalability issue.

Actually, that's only true in the naive implementation. If you actually wanted to scale something like this, you'd obviously have to pre-generate all the browser-specific stylesheets, serve them as static files, and use browser-sniffing to link in the appropriate one. That would eliminate any processing time concerns and, more importantly, allow you to offload your stylesheets to a CDN.

But overall, you're correct - this approach isn't as simple as it sounds. There are still trade-offs to be made.