Image replacement

There don't seem to be any browser incompatibilities.

Recently there has been much discussion about the "Fahrner Image Replacement". Although I liked the concept, I disliked the countless CSS variants that have popped up, because they are inherently unsafe and hacky. I feel we should use JavaScript instead of CSS. This page explains my script.

The idea of FIR is simple: Initially a page is served with text as the content of its headers. However, some clever (or not so clever) hacks are applied that hide this text and show an image instead. Theoretically browsers that can't handle advanced CSS, as well as screen readers, should not execute the replacement. Therefore the headers would remain accessible under all circumstances.

Assumptions

Unfortunately all CSS variants are making assumptions about screen reader behaviour. Joe Clark has admirably summarized the problem and his research. Screen readers turn out to support the CSS bits that everyone assumed they wouldn't support, so they hide the text. They can't show the image either, so accessibility is severely compromised. Clark's conclusion is that any CSS variant is inherently unsafe.

When I read a question about using JavaScript to enhance FIR, I realized that everyone was approaching this problem from the wrong way. We shouldn't use CSS at all to replace text by images. Instead, such a replacement job is a typical JavaScript task. In fact, it's ridiculously easy.

Nonetheless I'm not any better than the myriad CSS hackers who have preceded me: I make assumptions about screen readers. Although to me my assumption seems safer than all others, it may still be totally wrong. That's the chance you take when writing code for user agents you can't test in.

My assumption: Screen readers do not download images.

Example

All h3 texts on this page are replaced by images, if your browser allows it. The images are not examples of rarified design beauty. That's because I'm not a designer.

If you switch off images your browser will show normal h3s.

The script

I add an id attribute to any h3 that should be affected. It contains the source of the image to be displayed instead of the text. This script assumes that every h3 with an id should be replaced by an image.

<h3 id="fir_script">The script</h3>

This h3 expects the image pix/fir_script.gif for a replacement.

Then I run this script onload.

function init()
{
	var W3CDOM = (document.createElement && document.getElementsByTagName);
	if (!W3CDOM) return;
	var test = new Image();
	var tmp = new Date();
	var suffix = tmp.getTime();
	test.src = 'pix/fir_assumptions.gif?'+suffix;
	test.onload = imageReplacement;
}

function imageReplacement()
{
	replaceThem(document.getElementsByTagName('h3'));
}

function replaceThem(x)
{
	var replace = document.createElement('img');
	for (var i=0;i<x.length;i++)
	{
		if (x[i].id)
		{
			var y = replace.cloneNode(true);
			y.src = 'pix/' + x[i].id + '.gif';
			y.alt = x[i].firstChild.nodeValue;
			x[i].replaceChild(y,x[i].firstChild);
		}
	}
}

Browser compatibility

  Explorer 6 Windows Explorer 5.2 Mac Mozilla 1.6 Safari 1.0 Opera 7.50
Images on
Shows images Shows images Shows images Shows images Shows images
Images off
Shows text Shows text Shows text How do I turn them off? Shows text

Explanation

onload you should run the function init().

function init()
{

The first thing we do is checking for W3C DOM support. If it's absent we stop the script.

	var W3CDOM = (document.createElement && document.getElementsByTagName);
	if (!W3CDOM) return;

Detecting image support

Detecting W3C DOM support is not enough, though. We also have to see if the browser supports images. If it doesn't our script shouldn't run, since it would create all kinds of odd effects.

Therefore we generate an image and set its src. This is a classic preloading trick: the browser now fetches the image. This is the only support detection we need. If this test image loads succesfully, the browser supports images. I assume that a browser that doesn't support images doesn't download them, either.

So we activate the main image replacement routine only when this image has been loaded, ie. after its load event has taken place.

A browser problem here. If you return to this page by using the Back button, Explorer on Windows does not fire the onload event of cached images. Therefore we have to make sure that it fetches a new image every time by adding a suffix that contains the current time in milliseconds.

I don't like this feature of the script, it causes unnecessary HTTP requests, but at the moment I don't see a way around it.

	var test = new Image();
	var tmp = new Date();
	var suffix = tmp.getTime();
	test.src = 'pix/fir_assumptions.gif?'+suffix;
	test.onload = imageReplacement;
}

Defining the target elements

Once the test image has been loaded, it's safe to do wholesale image replacement. The function imageReplacement is called. It's an intermediate function to allow you to define several areas of the document where images should be replaced. This example script just replaces h3s.

function imageReplacement()
{
	replaceThem(document.getElementsByTagName('h3'));
}

However, if you'd also like to replace, say, all links in div id="nav", do

function imageReplacement()
{
	replaceThem(document.getElementsByTagName('h3'));
	replaceThem(document.getElementById('nav').getElementsByTagName('a'));
}

Image replacement

The function replaceThem() handles the actual image replacement. It is handed an array which it searches for elements with an id. If it finds one, the script replaces the element's firstChild (the text node containing the text) for an image with a name that's similar to the id.

To be on the safe side I also set the alt attribute. Theoretically it's unnecessary, since images will shown only when the browser supports images, but better safe than sorry.

function replaceThem(x)
{
	var replace = document.createElement('img');
	for (var i=0;i<x.length;i++)
	{
		if (x[i].id)
		{
			var y = replace.cloneNode(true);
			y.src = 'pix/' + x[i].id + '.gif';
			y.alt = x[i].firstChild.nodeValue;
			x[i].replaceChild(y,x[i].firstChild);
		}
	}
}