HTML5, Older Browsers and the Shiv
HTML5 introduced a few semantic elements that are not supported in older browsers. Some of these new elements are no different than generic block elements so they don’t pose any compatibility problems. All you need to ensure compatibility is to add a CSS rule to your website that causes the relevant elements to behave like block elements.
But Internet Explorer versions 8 and under pose a challenge. Any element not in the official roster of elements cannot be styled with CSS. That means we cannot make then behave like block elements or give them any formatting.
For example, the following code will not work.
<style>
section {display: block}
</style>
<section>This is on its own line.</section>
<section>This should appear on a separate line.</section>
But that’s not all. These new elements behave as if they don’t exist. For example, the following CSS won’t work, since the section
element won’t match the universal selector.
<style>
body * span {color: red}
</style>
<body>
<section>
<span>This should be red, but won't be red in IE 8.</span>
</section>
</body>
Fortunately for us, a workaround exists that allows Internet Explorer (IE) to recognize these new elements allowing them to be styled, and thus giving us full use of these new semantic tags. It’s a tool called HTML5Shiv.
As noted on the linked Google page, “shiv” and “shim” are interchangeable terms in this context.
But how did we go from IE not even acknowledging the existence of this element, to now being able to use it?
The trick is that calling document.createElement("section")
will suddenly cause IE to recognize the section
element. No one knows why, but it works and you don’t even need to use the node returned by that function.
But you need to make sure to call it early on in your website before any of those elements are used, otherwise it won’t work.
You will need to call it for each and every new HTML5 elements like so:
"abbr article aside audio bdi canvas data datalist details figcaption figure "+
"footer header hgroup main mark meter nav output progress section " +
"summary template time video"
.replace(/w+/g, function(a){ document.createElement(a) });
Notice we’re using the replace
method of the string
object to succinctly iterate over each contiguous length of characters matched by the regular expression and executing the callback function for each character block which in turn calls createElement
.
Here on in, we’ll call this method, “shivving the document”, so that the document can render the new HTML5 elements.
Now our previous two HTML examples work. But that’s not all there is to it.
Pitfall 1: HTML5 and innerHTML
If HTML is being generated using innerHTML
and it is called on a node not currently attached to a document (AKA an orphaned node), then it’s deja vu all over again. The following two examples will not render the section
element, even though it’s run on a document already shivved.
var n1 = document.getElementById("n1");
n1.parentNode.removeChild(n1);
n1.innerHTML = "<section>Sect 1</section>"; //won't work
var n2 = document.createElement("div");
n2.innerHTML = "<section>Sect 2</section>"; //won't work
In the second example above, if we append the node to the document first before calling innerHTML
, then it will work:
var n2 = document.createElement("div");
document.body.appendChild(n2);
n2.innerHTML = "<section>Sect 2</section>"; //works
We can conclude that although we shivved the document earlier on, orphaned elements do not benefit from the shiv when calling innerHTML
.
What can we do? For starters, whenever we need to set innerHTML
we should append it to the document first. An alternative is to first shiv the document
property of the orphan before working with the orphan.
First let’s put our shiv in its own function.
function iehtml5shiv(doc) {
"abbr article aside audio bdi canvas data datalist details " +
"figcaption figure footer header hgroup main mark meter nav " +
"output progress section summary template time video"
.replace(/w+/g, function(a){ doc.createElement(a) });
}
The next time we have an orphan element, we can do this:
var n1 = document.createElement("div");
iehtml5shiv(n1.document);
n1.innerHTML = "<section>Sect 1</section>"; //works
Notice how it’s just like shivving the document, but on the document
property of the element. And notice we’re accessing document
instead of ownerDocument
. Both are different things as shown here:
alert(n1.document == document); //false
alert(n1.ownerDocument == document); //true
Now we have two methods to make sure our call to innerHTML
works when handling HTML5 elements.
Pitfall 2: cloneNode
It appears our cousin cloneNode
is also susceptible to losing its shiv. Any HTML5 elements which are cloned, or have had their parents cloned, will lose their identity.
Notice how the below element has colons in its nodeName
, meaning it’s being confused for an element from another namespace.
var n2 = n1.cloneNode(true);
alert(n2.innerHTML); //outputs: <:section>Sect 1</:section>
This happens even if the node was already attached to the document.
There isn’t much we can do here except roll out your own implementation of cloneNode
, which is trivial enough.
Pitfall 3: Printing
Whenever you print a webpage, IE appears to generate a new document before printing which means all the shiv workarounds are not preserved.
There isn’t much you can do to mitigate this. The HTML5Shiv tool works around this by listening for the onbeforeprint
event and replacing all the HTML5 elements on the page with normal elements and then doing the reverse on the onafterprint
event.
Thankfully, the HTML5Shiv tool does that nicely for us.
References
- The HTML5Shiv tool: https://github.com/aFarkas/html5shiv
- The story of the HTML5 Shiv: http://paulirish.com/2011/the-history-of-the-html5-shiv/