Heygrady (there's a new blog)

I don't get It.

CSS Text Shadows in Internet Explorer

Permalink

CSS3 introduced a new text-shadow property that allows for, well, shadows to be placed on text. As of February 2011, text-shadow is only supported in 48% of browsers in use. Internet Explorer 8 and below do not support text-shadow; surprisingly Internet Explorer 9 doesn't either. Because the majority (90%) of non-IE browsers already supports text-shadows natively, it's most interesting to try and polyfill IE. I've created a Textshadow jQuery plug-in for just this purpose. Read below for an overly thorough explanation of hacking IE filters to get a convincing text shadow.

Goals

The goal is to create a text-shadow polyfill for IE 9 and below and other browsers that lack support. It must look similar to the implementation in supported browsers and follow the W3C specification as closely as possible.

  • Shouldn't affect content layout
  • Should flow with content
  • Should follow W3C specification

Text Shadow

Below is the example of the goal: the native text-shadow as rendered in Chrome 9. The shadow is red to add the greatest contrast between the black text and the white background.

text-shadow Image
text-shadow in Chrome 9
(view live example)
textshadow.htmlGist page
...
<div class="box">
<h1>Some text that wraps and has a <b>s<i>h<b>a</b>d</i>ow.</b></h1>
</div>
...

For these examples the simple HTML above is used. The .box is used for forcing the text to wrap and the funky b and i elements in the word "shadow" are for testing how shadows are applied to child content.

textshadow.cssGist page
h1 {
text-shadow: 2px 2px 2px #ff0000;
}

None of the browsers that support text-shadow require a vendor prefix, making text-shadow extremely easy to implement. As noted above, this will not work in IE including version 9.

Using IE Filters

While IE doesn't directly support the CSS3 text-shadow property, it does support proprietary filters. Of course these filters have drawbacks; namely, IE 8 and below apply them very slowly and they have poorly documented features. For instance, the IE filters will apply to text unless the element has a background applied; the filters will also apply to borders if they are present. Another issue that will become clear later is that IE renders the text with alpha-transparent artifacts that make the text harder to read.

There are a few IE filters that seem promising. Most often the shadow filter is proposed as an alternative to native text-shadow although the most compelling articles use a combination of blur and glow to achieve a text-shadow effect (another great example only uses blur).

Shadow

Shadow Filter Image
Shadow Filter in IE 8
(view live example)

The example above is the proprietary shadow filter in Internet Explorer. This filter takes the original color and fades it to transparent. The filter takes three arguments for color, direction and strength. Direction is taken as an angle. To approximate a 2px X and 2px Y offset, a direction of 135 degrees (135 degrees is 90 degrees plus 45 degrees (duh?)) and strength of 2 is chosen.

  • color- hex color without leading hash (#) character.
  • direction- angle in degrees. Zero degrees is straight up.
  • strength- length in pixels without any units.
shadow.cssGist page
h1 {
filter: progid:DXImageTransform.Microsoft.Shadow(direction=135,strength=2,color=ff0000);
}
Shadow Filter with White Text Image
Shadow Filter in IE 8 with White Text
(view live example)

Changing the text to white will reveal some of the artifacting issues mentioned above. There are partially black pixels dotted around the perimeter of the text. This is the result of IE using a black matte behind the alpha transparent pixels in the text. I haven't fully experimented with how to remove this artifacting but it will likely involve turning off font smoothing in IE if it's possible.

Results

  • The shadow gradient produced is not a blur.
  • The gradient treatment causes the shadow to appear pink instead of red.
  • Shadow does not follow W3C Spec. With a small strength(like 2px) the shadow looks similar to the native text-shadow. With a higher strength it becomes apparent that the shadow strength is not an offset.
  • The artifacting is distracting and makes the text harder to read.

DropShadow

Dropshadow Filter Image
Dropshadow Filter in IE 8
(view live example)

Dropshadow will make a copy of the text and offset it behind the original text. This is very similar to the W3C spec for text-shadow with the exception of blur being unsupported. As shown above, the shadow is not anti-aliased.

  • color- hex color without leading hash (#) character.
  • offX- length in pixels without units for the offset along the X axis.
  • offY- length in pixels without units for the offset along the Y axis.
dropshadow.cssGist page
h1 {
filter: progid:DXImageTransform.Microsoft.DropShadow(offX=2,offY=2,color=ff0000);
}
Dropshadow Filter with White Text Image
Dropshadow Filter in IE 8 with White Text
(view live example)

With white text it is evident that dropshadow suffers from the same black pixel artifacting as shadow.

Results

  • Blur isn't supported in dropshadow.
  • Dropshadow otherwise follows the W3C spec for dropshadows by creating a copy of the text.
  • The artifacting is distracting and makes the text harder to read.

Glow

Glow Filter Image
Glow in IE 8
(view live example)

Glow is yet another filter that could be used to replace text-shadow; however, it's obvious that glow isn't very much like text-shadow at all. But it could be used to make text stand out from a background, which is essentially what text-shadow is used for. The color gradient effect is similar to the shadow filter but for glow, the color is omni-directional.

It should be noted that the glow affects the positioning of the text. A 2px glow pushes the text down 2px and to the right 2px. A 20px glow pronounces the issue, predictably pushing the text 20px down and to the right. The other filters, shadow and dropshadow, exhibit the same behavior when the shadow is projected up or to the left. This can cause unexpected cross-browser display issues.

  • color- hex color without leading hash (#) character.
  • strength- length in pixels without any units.
glow.cssGist page
h1 {
filter: progid:DXImageTransform.Microsoft.Glow(color=ff0000,strength=2);
}
Glow Filter with White Text Image
Glow Filter in IE 8 with White Text
(view live example)

Again, white text reveals the same distracting artifacting as shadow and dropshadow.

Results

  • Glow isn't at all like the W3C specification for text-shadow.
  • The gradient treatment causes the glow to appear pink instead of red.
  • Unexpected text offset will cause cross-browser display issues.
  • The artifacting is distracting and makes the text harder to read.

Blur

Blur Filter Image
Blur in IE 8
(view live example)

Blur is perhaps the most frustrating filter because it is both exactly what is needed and not at all appropriate. The blur effect is very similar to the blur other browsers use for text-shadow. But of course, the text is rendered unreadable. It should also be noted that the blur offsets the text the same way glow does. For this example the text is shifted 2 pixels down and to the right by the blur. The larger the pixelRadius is, the larger the unintended offset will be, similar to the glow filter.

  • pixelRadius- length in pixels without any units. This is equivalent to the blur-radius argument for text-shadow.
  • makeShadow- turns the text black no matter what color it was originally. Never use this.
  • shadowOpacity- only applies when makeShadow is set to true. Sets opacity and expects a value between 0 and 1. Never use this.
blur.cssGist page
h1 {
color: #00ff00;
filter: progid:DXImageTransform.Microsoft.Blur(pixelradius=2);
}

Results

  • Blur creates the desired blur effect.
  • Blur obscures the original text which isn't exactly desirable.
  • Blur doesn't allow for color to be set directly, although it does have a useless setting that will turn the text black.

Making Blur Work

Blur Filter with SPAN Image
Blur Filter in IE 8 with SPAN
(view live example)

Using a few span tags and duplicating the text allows for the blurred text to be placed behind the original text. By wrapping the original text in a span, duplicating the original text and adding it as a second span, and wrapping both in a third span, it becomes simple to position the blurred text as a shadow. As is shown above, using absolute positioning for the shadow can have undesired results. The absolute positioned span is collapsed to be as wide as the longest word. However, it is clear that the word "Some" has an appropriate text shadow. It is possible to support shadow color simply by setting the color property on the blurred text.

blurspan.htmlGist page
...
<h1>
<span class="shadow">
<span class="shadow-orig">Some text&hellip;</span>
<span class="shadow-copy">Some text&hellip;</span>
</span>
</h1>
...

The .shadow wrapper is given a position: relative to allow for proper positioning of the .shadow-copy. The .shadow-copy is given absolute positioning and a z-index of 1. The .shadow-orig is given position: relative and a z-index of 2 in order to place it above the .shadow-copy. The top and left properties can be applied to .shadow-copy to approximate the X and Y text-shadow offsets. In this case, top and left are set to zero because the blur filter already shifts the shadow by 2px as described above.

Fixing the Wrapping Issues

Instead of wrapping a long string of text in the span elements, it is more reliable to wrap individual words. This ensures that the .shadow-copy is always an appropriate width without having to set a hard width property in the CSS. Setting a hard width on the span would cause issues if the text-size were to change or the containing block changed size dynamically.

textshadow-plugin.htmlGist page
<div class="box">
<h1><span class="ui-text-shadow"><span class="ui-text-shadow-original">Some </span><span class="ui-text-shadow-copy">Some </span></span><span class="ui-text-shadow"><span class="ui-text-shadow-original">text </span><span class="ui-text-shadow-copy">text </span></span><span class="ui-text-shadow"><span class="ui-text-shadow-original">that </span><span class="ui-text-shadow-copy">that </span></span><span class="ui-text-shadow"><span class="ui-text-shadow-original">wraps </span><span class="ui-text-shadow-copy">wraps </span></span><span class="ui-text-shadow"><span class="ui-text-shadow-original">and </span><span class="ui-text-shadow-copy">and </span></span><span class="ui-text-shadow"><span class="ui-text-shadow-original">has </span><span class="ui-text-shadow-copy">has </span></span><span class="ui-text-shadow"><span class="ui-text-shadow-original">a </span><span class="ui-text-shadow-copy">a </span></span><b><span class="ui-text-shadow"><span class="ui-text-shadow-original">s</span><span class="ui-text-shadow-copy">s</span></span><i><span class="ui-text-shadow"><span class="ui-text-shadow-original">h</span><span class="ui-text-shadow-copy">h</span></span><b><span class="ui-text-shadow"><span class="ui-text-shadow-original">a</span><span class="ui-text-shadow-copy">a</span></span></b><span class="ui-text-shadow"><span class="ui-text-shadow-original">d</span><span class="ui-text-shadow-copy">d</span></span></i><span class="ui-text-shadow"><span class="ui-text-shadow-original">ow.</span><span class="ui-text-shadow-copy">ow.</span></span></b></h1>
</div>

The above is an example where the simple threesome of span elements has been applied to every individual word. Note that in the word "shadow", a span is nested inside each b and i element as well.

Turning it into a jQuery Plugin

Textshadow Plugin Image
Textshadow Plugin in IE 8
(view live example)

I have created a simple jQuery plugin called jQuery Textshadow that automatically wraps each word in the spans as shown above and applies the appropriate blur filter and positioning for the shadow. It even supports RGBA and HSLA by converting the values to a hex color and applying the alpha filter to the shadow. The plugin requires that a small CSS file also be included on the page in order to set up some base styles for the spans.

jquery.textshadow.cssGist page
.ui-text-shadow {
position: relative;
}
.ui-text-shadow-original {
position: relative; /* above copy */
z-index: 2;
text-shadow: none;
}
.ui-text-shadow-copy {
position: absolute;
z-index: 1; /* below original */
/* default positioning */
left: 0;
top: 0;
/* turn off shadow */
text-shadow: none;
/* turn off selection */
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}

Interesting Notes

Currently, the plug-in assumes it is being applied in IE only and doesn't do any feature detection. It should be applied using a tool like Modernizr or IE conditional comments. It's not particularly speedy, especially if there are dozens of tags that need the shadow applied. The slowest part is actually wrapping the text nodes with the replacement HTML. It's acceptably fast in Internet Explorer 9 and noticeably slow in Internet Explorer 8.

Curiously, Internet Explorer 9 also applies a cleaner shadow that is closer to text shadow in other browsers. The blur in IE 8 is noticeably muddier than the browsers that support text-shadow. See the images below for comparison.

Textshadow Plugin IE 8 Image
Textshadow in IE 8
Textshadow Plugin IE 9 Image
Textshadow in IE 9
Textshadow Chrome 9 Image
Textshadow in Chrome 9
Textshadow Firefox 3.6 Image
Textshadow in Firefox 3.6
Textshadow Safair 5 Image
Textshadow in Safari 5
Textshadow Opera 11 Image
Textshadow in Opera 11

Comments