CSS Text Stroke
August 28th, 2007
Text Stroke is the cool, nice-looking effect where black text characters are displayed on a white surrounding to increase readability on a non-uniform coloured background. For example text on Google Maps is displayed that way, just have a look at the place names in the banner of this page. A way to have this effect on an image is to use the Glow effect in Photoshop.

I thought it would be great to have a way to display any text on a web page that way. As a matter of fact, there seems to be a CSS stroke or text-shadow property, but it isn’t implemented yet, except maybe in some SVG implementations or in Safari. Additionally, Safari developers can have text stroking thanks to Webkit.
But for the rest of us, I’ve put together a quick javascript hack to get text stroking on any piece of text on your web page’s template. The trick is to repeat the text element several times in white color in the background, each instance being slightly moved from the original text position. I first tried with 4 instances of the text (one in each principal direction), which worked pretty well for bold fonts but looked a bit weird with thin fonts, so I’m now using 8 instances of the text.
How it works
The method is javascript based, i.e. your document is crawled on window.load and the relevant elements are dynamically stroked via DOM manipulation. So Google or any web crawler won’t see the text elements more than once.
The one big limitation is that I couldn’t get the method to work on an element that’s not absolutely positioned via CSS. It might or might not be a problem for you, depending on your design. And usually you’ll be able to tweak your layout a bit so that the relatively positioned elements that you wish to stroke are made absolute (see my wordpress example to see how I’ve done that). In any case if you see a solution to that issue please let me know.
Now let’s see in detail how the hack works: 8 CSS span element definitions are created, each one sliding the text of 1.5 pixel in one direction (up, up-right, right, down-right, and so on).
span.rawtext { position:absolute; top:0px; left:0px;}
span.stroke1 { position:absolute; color:white; top:-1px; left:-1px;}
span.stroke2 { position:absolute; color:white; top:1px; left:1px;}
span.stroke3 { position:absolute; color:white; top:-1px; left:1px;}
span.stroke4 { position:absolute; color:white; top:1px; left:-1px;}
span.stroke5 { position:absolute; color:white; top:2px; left:0px;}
span.stroke6 { position:absolute; color:white; top:-2px; left:0px;}
span.stroke7 { position:absolute; color:white; top:0px; left:2px;}
span.stroke8 { position:absolute; color:white; top:0px; left:-2px;}
A javascript file contains a function that selects the relevant text elements according to a given selector and modifies them via DOM scripting so that, to each one of them, 8 instances of the same white text are added in the background.
You can have a look at the javascript file here, the main function being:
function processElement(v){
var content=v.innerHTML;
for (j=1;j<=8;j++){
var d=document.createElement("span");
d.className = "stroke"+j;
d.innerHTML = content;
v.appendChild(d);
}
v.removeChild(v.firstChild);
var dd=document.createElement("span");
dd.className = "rawtext";
dd.innerHTML = content;
v.appendChild(dd);
}
Finally, a small piece of javascript code is added to the page where you want to apply the hack (as well as the usual links to the .js and .css file).
<script type="text/javascript">
window.onload=function(){
if(!NiftyCheck())
return;
processTextStroke("div#headerimg a");
processTextStroke("div.description");
}
</script>
This last part basically just checks that your browser supports DOM scripting and process the desired text elements, given by the corresponding CSS selectors.
Download
You can download the files here. I also wrote a Wordpress plugin called WP CSS Text Stroke, feel free to download it from here.
Credits and Conclusion
I think that this hack clearly demonstrates the power of DOM scripting. I drew a lot of inspiration from the implementation of Nifty Corners and even used some of their code so thanks to them. I drew inspiration from there as well.
February 16th, 2008
Update: the Wordpress plugin finally made his way to the Wordpress extensions directory.
January 6th, 2008 at 1:00 am
hey uh is this seo friendly? or will google see 9 headings?
January 7th, 2008 at 10:32 am
I’m not sure. If the crawlers have javascript enabled (which I am not sure they do) they will see 9 headings, but if they don’t they will see the regular header.
I am not a SEO expert so I don’t know if Google apply javascripts to pages. If you know the answer to that question please comment.
January 14th, 2008 at 3:02 pm
lol and if i read properly
“The method is javascript based, i.e. your document is crawled on window.load and the relevant elements are dynamically stroked via DOM manipulation. So Google or any web crawler won’t see the text elements more than once.”
hopefully it will be fine
January 14th, 2008 at 3:04 pm
by the way (sorry for double post) this is an awesome lil script thanks for creating it, its awesomely effective
February 20th, 2008 at 9:24 am
I’m glad you got this out there since it solves the problem I was having with using a random image for the header.
I’m having some grief customizing it though. Depending on the selector I choose, the layout is broken. And when I got very specific with the selector, IE looks fine but Mozilla is bad! Crazy — it’s usually the other way around. It would be nice if someone could explain which elements have to be relative and what that does to their positioning.
I have a few suggestions for the plugin. You could put the css inline in the init function. That way you could have an option for the color and can get rid of the awful hard-coded path to the plugin directory. If you don’t do that, at least consider using
ABSPATH . PLUGINDIR . '/' . plugin_basename($file)or something a bit more flexible.Another thing is assigning the onload function without checking to see if something is already assigned. I have the Google Maps code on some pages and that needs the onload assignment also.
I added a second parameter to the javascript to control the number of strokes. On smaller text I use only 4, but on the bigger text I use 8.
February 22nd, 2008 at 2:18 pm
Thanks for the feedback.
Yeah, I know it’s a bit rough at the edges
I’ll look into implementing the changes you suggest when I have some time.
By the way, how would you add something to the onload function instead of just reassigning it?
February 22nd, 2008 at 9:13 pm
Hey, you added a really cool peel script to your page! That wasn’t there the other day, was it?
I’ve learned a lot in the last 2 days…been hard at it.
First the selectors: I knew this, but had to experiment to have it beat into my head — make the encompassing (parent) element relative. Then whatever you want absolutely positioned on top of that is a child of it because the absolute position is in reference to the parent. The relative position is in reference to where it would land in the flow of the document. So my plan is to get the text you want to stroke positioned how you want it with the relative positioning, and then add the text stroke effect and it shouldn’t move around. If so, you need to add another child element.
Second, there’s a few useful WordPress codex pages that tell you how to do options and their admin panels. It’s pretty easy. There’s also a lot of stuff in WordPress that the codex doesn’t mention. One is a class that loads scripts (and their dependencies) for you. The javascript code that you have looking for selectors doesn’t handle all cases (several of which my code wanted). Since prototype comes with WordPress now, you could use it and it has that selector stuff built in. (My WordPress 2.2 has two versions of prototype actually.)
Third, about assigning the onload function, I thought I read some code way back that treated it like a stack, always saving the old value and calling it after your own work is done. But I don’t know where or what that was. Looking at the prototype code I saw that they provide ways to set it, but I’m not sure if it hurts anything already set there.
February 23rd, 2008 at 3:21 am
I did add this page peel pretty recently
I’m preparing for the release of MapMyGlobe v2, which will be a fantastic improvement on the current one
About CSS selectors: yeah, Prototype’s selector is probably best coded than mine. By the way, a funny thing about that happened recently: I was using the $$() function from Facebook in one of my Facebook scripts, and one day it was broken (probably not from Facebook’s perspective, but from mine), so now I have to redefine my own