Tuesday, September 14, 2010

IE Hacking

#IEroot: The New Star

A wrapper may be made to enclose the content of a page, and then you can write descendant CSS rules that mention that wrapper's ID or class name in the selector. But what if only IE thought that wrapper existed? Then those rules would only work for IE, while other browsers would ignore the rules completely.
This is more or less how it was with the Star-HTML hack, where a mysterious wrapper seemed to be surrounding the HTML element in IE browsers. This made possible a very clean and easy selector construction: * html #myelement {}. Now this trick is different because IE7 has lost the mystery wrapper and will not read that selector as IE6 does. What we need is a way to make a new wrapper to use that only IE can see. But how can this be done? It so happens that you can use Conditional Comments to make IE think there is a wrapper between the body and the entire page contents. IE's view of the page element hierarchy will become different from all non-IE browsers, letting us write CSS to take advantage of the difference. The code is not very difficult, but the syntax is a bit different and must be correct for the method to work.
Conditional Comments behave just like simple HTML comments, but they have a specific syntax that IE/Win browsers can recognize. When that syntax is exactly correct, IE/Win browsers will look inside the comment and parse whatever is inside.
The starting tag for the new wrapper will go directly after the body start tag, and the wrapper's end tag is placed directly preceding the body's end tag. Each of these div tags will be wrapped in a conditional comment, allowing only IE/Win browsers to parse those div tags.
<!DOCTYPE your favorite doc type> <html> <head>...</head> <body> <!--[if IE]> <div id="IEroot"> <![endif]--> <p id="IE">This browser is IE.</p> <p id="notIE">This browser is not IE.</p> <!--[if IE]> </div> <![endif]--> </body> </html> The first block of code after the body tag has a starting div tag with an ID of #IEroot inside a conditional comment; remember, only IE sees this div.
The last block of code before the closing body tag is a closing div tag inside another conditional comment; this closing div tag will match the starting div tag in the first block of code. Again, only IE will see this closing tag.
Between the opening and closing of the #IEroot div is where all of the normal page markup will go. In this example it is just a box with some content, but the idea is for the #IEroot div to be the ancestor of all page content.
Using CSS, style the page as usual for non-IE browsers. Then in the same stylesheet, use #IEroot as a prefix in descendant selectors to target specific rules at IE. The example below styles the div called #anyelement to have a red border in non-IE browsers, and a blue border in IE.
/* all browsers make border red */ #anyelement { border : 2px solid red; } /* all browsers see this, but only IE thinks #IEroot exists as an element and makes border blue */ #IEroot #anyelement { border-color : blue; } The first rule is a simple rule to apply a red border to #anyelement; this rule is seen by all browsers, even IE.
The second rule changes the color of #anyelement's border to blue, but the selector is a little different than the first rule. The second rule's selector is a descendant selector; it's interpreted as: the element with the ID of "anyelement" that is a descendant of the element with the ID of "IEroot" should have the following rules applied.
Since #IEroot was created using conditional comments and only IE can see the contents of conditional comments, then in IE, #anyelement is a descendant of #IEroot and the descendant selector matches. Therefore, this rule will apply to IE, making the border of #anyelement blue for all IE browsers, versions 5 through 7. And because #IEroot is the first div and wraps the entire page, it can be used throughout the stylesheet to target rules at IE. This is used just like the star-HTML hack was used, except that you cannot target the body tag with this method because the body element is not and cannot be a descendant of #IEroot.
Here is a live demo page. Be sure to view in both IE and non-IE browsers.
Using this technique you can target part of a stylesheet at IE with confidence that this method won't be affected by future browser releases.

The Anti-IEroot Rule

Normally the CSS code will be clean, and the #IEroot rules will simply override key rules in the main styles to make IE behave correctly. Some coders will want the option to write a rule that all non-IE browsers will read, but that IE will not. A Child combinator can be used in a certain way:
body>#wrapper #anyelement { styles for non-IE only... } Just add a prefix to any selector for a page element (#anyelement) with body>#wrapper. Of course, you will need to have a #wrapper element in the page, or some other major element that is a direct child of the body. The clean HTML should have #wrapper directly inside the body element so that the selector above will work.
The Child combinator (>) selects the element on the right only if it is a direct child of the element on the left.

Hiding From IE

IE6 and below won't read the rule, for the simple reason that those browsers don't support the child selector itself. IE7 does support it, but IE7 also happens to think there is an element called #IEroot! For IE7, a selector like body>#IEroot>#wrapper would work, but the selector we described above does not include #IEroot. IE7 thinks #IEroot is in the HTML, so IE7 ignores the rule. Thus all IE/Win versions are blocked from seeing this type of selector.

More on Conditional Comments

Conditional comments allow special syntax constructions for checking the IE version and are proprietary to Internet Explorer for Windows. A conditional comment is an HTML element that, in IE may conditionally be read, but, to all other browsers looks like an HTML comment and is ignored. Here is an example of some markup with conditional comments:
<!--[if IE]> <p>Only IE shows this paragraph.</p> <![endif]--> <p>A paragraph that all browsers display</p> In the above example, the first block of code has a paragraph of text inside a conditional comment. Internet Explorer has been designed to examine all HTML comments for certain syntaxes, and when it finds such syntax IE will simply read and parse whatever is inside the conditional comment. Because conditional comments are hidden inside syntactically correct HTML comments, all other browsers just see a comment and do not display the paragraph contained within it.
The next paragraph is outside the conditional comment and displays normally.
If you want to test your conditional comments in different versions of IE, one simple method is to have multiple versions of IE installed as standalones. But there is a problem with standalones: they don't support conditional comments correctly. This article will help you fix conditional comments and many other things in your standalone IE's.
If you haven't installed multiple IE's yet, go here to download a fast, easy, and complete multiple IE installer with all the tweaks done for you.

Deeper with Version Numbers

Conditional comments support targeting of specific browser versions, which means divs can be created that will be used in CSS selectors to target not just IE in general, but specific versions of IE. Here is an example of how this might be done:
<!--[if gte IE 7]> <div id="ie7andup"> <![endif]--> <!--[if IE 6]> <div id="ie6only"> <![endif]--> <!--[if IE 5.5000]> <div id="ie5-5only"> <![endif]--> <!--[if lt IE 5.5000]> <div id="ie5-01only"> <![endif]--> <div id="anyelement">a box with some content</div> [... more page content ...] <!--[if IE]> </div> <![endif]--> Only one of the four conditional divs will be created in IE, depending on the version of the viewing browser, and none of them will be created in other browsers. Note that only one generic all-IE div end tag is required, so no need to have multiple CC's for this task.
The first div, #ie7andup, will be created in IE version 7 and up. The "gte" means "Greater Than or Equal to."
The second div, #ie6only, will be created in IE version 6 alone.
The third div, #ie5-5only, will be created in IE version 5.5.
The fourth div, #ie5-01only, will be created in IE version 5.01. The "lt" means "Less Than." IE4 is not included, since conditional comments were not introduced into IE until version 5.
When targeting IE5.5 you must add the zeros to the 5.5 or the browser may ignore the conditional comment. <!--[if lt IE 5.5]> is not good enough, it must be <!--[if lt IE 5.5000]>. There is no good reason for this, that's just how it works. gte IE 7 stands for "greater than or equal to IE7," while lt IE 5.5000 stands for "less than IE5.5." When writing these CC's, exact conformance to the syntax is required. Any error will cause the CC to become a normal HTML comment.
The corresponding CSS looks like this:
/*** All browsers ***/ #anyelement { border : 2px solid black; } /*** For IE 7 and up ***/ #ie7andup #anyelement { border-color : blue; } /*** For IE 6 ***/ #ie6only #anyelement { border-color : green; } /*** For IE 5.5 ***/ #ie5-5only #anyelement { border-color : purple; } /*** For IE 5.01 ***/ #ie5-01only #anyelement { border-color : red; } See a live demo of the method. You will need multiple versions of IE to view all the variants.
Not only is IE styled differently, but different versions of IE are styled differently. For example, this method makes it easy to fix the box model problem in IE 5.x while not affecting IE 6 or 7. Each new #IEroot ID is customized to indicate what IE versions are targeted, in effect "labeling" each special IE rule by version number. This helps when later doing site maintenance, and team environments benefit as well from the reduced potiential for confusion.