Dynamic Text Replacement

Text styling is the dull headache of web design. There are only a handful of fonts that are universally available, and sophisticated graphical effects are next to impossible using only standard CSS and HTML. Sticking with the traditional typefaces is smart for body text, but when it comes to our headings — short, attention-grabbing blocks of text — it would be nice to have some choice in the matter. We’ve become accustomed to this problem and we cope with it either by making the most of the few fonts we have, or by entirely replacing our heading-text with images.

Article Continues Below

Most sites that replace text with images do so using hand-made images, which isn’t so terrible when there are a set number of headings, but it quickly becomes unmanageable on a site that is updated several times per day. However the replacement is performed, each image needs to be bound to the text it is replacing. That binding usually manifests itself as an <img> tag, an embedded style sheet, or a custom id attribute. And over time, through layout changes and redesigns, that binding needs to be managed by someone.

We can forget all that nonsense. No more <img> or <span> tags, no more id attributes or wasted time in Photoshop, and no more messy CSS hacks. Using JavaScript and PHP, we can generate accessible image-headings using any font we like. And we don’t have to change the structure of our HTML or CSS at all.

View the demo to see Dynamic Text Replacement in action. Then read on to find out how you can add the same functionality to your site.

PHP#section2

This small PHP script (available here) will deliver a dynamic PNG image to our browser whenever it’s asked to. Before we set it to work, though, it needs to be customized for your specific purpose. The first seven lines of code in the script serve this purpose:

  $font_file = 'font.ttf' ;
  $font_size = 56 ; 
  $text_color = '#ffffff' ;
  $background_color = '#000000' ;
  $transparent = true ;
  $cache_images = true ;
  $cache_folder = 'cache' ;
  • The $font_file variable must be set to the local path (not the URL) of a True Type (TTF) or Open Type (OTF) font on your web server. This is the font that your images will be created with; you’ll need to upload it to the web server from your own computer.
  • $font_size, unsurprisingly, refers to the size of the font in points.
  • $text_color and $background_color are hexadecimal color codes that indicate the color of the text, and color of the image’s background, respectively.
  • When $transparent is set to true, the edges of the image’s text will be blended with the $background_color to prevent anti-aliasing, and the actual background color will be entirely invisible.
  • By setting $cache_images to true, and $cache_folder to the local path of a writable directory on your web server, this script will save each image that it creates, caching them for later use. This can significantly speed up delivery of images to your visitors, and is particularly important on shared, or high-traffic servers.

To install this script, upload it to a web server that is configured with PHP support. Specifically, you will need PHP version 4.3 or higher, compiled with support for the GD graphics library, 1.6 or higher. If none of that means anything to you, email those requirements to your web host and they’ll tell you if your server is compatible.

Although we used PHP to construct the images in this implementation, your website does not need to be actively using PHP to take advantage of this technique. Regardless of how you generate your HTML pages, whether they’re edited by hand or through a CMS, you can use this technique as long as you can insert a <script> tag into the <head> of your documents. I’ll explain that detail further, below.

Please note that what can be done with PHP can often be done with other tools as well. Perl, ASP, Java servlets, and other server-side programming languages would also be good candidates for generating custom images. PHP is an excellent choice because of its wide availability, platform independence, and easy learning curve. Consider the alternatives if you require something that PHP doesn’t provide or if you choose to create your own image-generation code from scratch. It might be simpler, however, to just adapt the PHP code presented here.

One thing that our customization of the script did not include was the text that it should use to generate our custom images. That’s because the text that we’re using to produce these images will be passed to the script via its URL. For example, loading the URL heading.php?text=URLs Are Fun will produce a graphic that reads “URLs Are Fun.” And they are. But we’ll never need to write them ourselves, because JavaScript will do it for us.

JavaScript#section3

Download the JavaScript source file here.

This technique borrows heavily from Peter-Paul Koch’s JavaScript Image Replacement (JIR) method. The premise of JIR is very simple: Many CSS coders exploit browser bugs to hide CSS styles from those browsers. These hacks are akin to limited conditional statements in their code, turning CSS into a crude programming language. Rather than using this language-of-bugs to compensate for browser differences, Koch and others have put forth the idea that JavaScript — an actual scripting language — could more intelligently and accessibly perform the same task. This is wonderful for our purposes because JavaScript also gives us more flexibility. Specifically, we’ll be using it to replace text with images that don’t even exist yet.

When the page first loads, the script will attempt to load a small (1×1 pixel) test image. If this test is successful, we can conclude that the visitor’s browser supports the display of images, otherwise it would not have wasted bandwidth downloading it. This is the crux of JIR: By testing for image support, we can immediately determine whether or not our visitors have any use for stylized headings. If they do not, the script will stop right there.

Assuming the visitor’s browser supports images, the script then waits until the page is entirely finished loading, because it can’t replace text that hasn’t been downloaded yet. Once the HTML is finished loading, our script will search it for specified elements (<h2>, <span>, etc.) and replace the text inside of them with an <img> tag. This dynamic <img> tag has its alt attribute set to the original text, and its src attribute set to the URL of the PHP script that we just installed. The PHP script then sends back a custom PNG image, and voila: custom headings.

Weighing in at a hefty eight kilobytes, there’s a lot of stuff going on in this corner of the ring, but there are only two lines that need to be customized before the script will work.

  replaceSelector("h2","heading.php",true);
  var testURL = "test.png";

The replaceSelector function accepts three parameters: The first is the CSS-style selector that indicates which elements should be replaced. This selector may be almost any valid CSS selector, including id, class, element and attribute selectors.

The second parameter is the URL of our custom PHP script.

The third parameter is a true/false value that indicates whether word-wrap should be turned on for this replacement. When this flag is set to true, headings are broken into multiple images, one for each word. When it is false, only a single, non-breaking image is generated for each heading.

replaceSelector should be called once for each group of elements you want replaced by a custom image. The URLs in these lines can be absolute (http://…) or relative to our HTML file (path/filename).

The testURL variable needs to be set to the URL of a small (1×1 pixel) test image.

Once these lines are set correctly, you can upload the JavaScript file to your web server, and apply it to your web pages by adding the following line to their <head> tags.

  <script
    type="text/JavaScript"
    src="replacement.js">
  </script>

Make sure the src attribute in that line points to the location that you uploaded the JavaScript file to.

That’s all that’s required to get dynamic text replacement working; we can stop right there if we want to. But there are a few optional improvements we may want to make before calling it quits.

Print Versions#section4

As previously seen here in ALA, many sites are now employing specialized printer style sheets to give their visitors better hard copies of their content. In many cases this involves reversing the process of image replacement so that the printed copy of a page uses actual fonts rather than graphics, which often look poor on high-resolution printers. Unfortunately, JavaScript falls short of solving this problem. Once we’ve replaced our text with an image, it’s impossible to reverse that process specifically for printing purposes, so we need to find another solution.

Instead of trying to reverse our replacement process, we can do a little planning ahead. Along with inserting an <img> tag into our headings, we can also insert a <span> tag that contains the original heading text. And we can set that span’s display property to none, so that it doesn’t show up onscreen. Now we have two copies of our original text: One in a visible image, and one in an un-displayed span. By giving each of these elements identifiable class attributes (“replacement”, and “print-text,” respectively), and by adding in a print-specific style sheet, we can swap their display properties when they’re printed.

The following style sheet (download a sample CSS file here) could be used to generate an appropriate print version of your page:

  span.print-text {
    display: inline !important;
  }
  img.replacement {
    display: none;
  }

Once we’ve uploaded this style sheet -to our web server, we only need to change two lines in our JavaScript to make it work:

  var doNotPrintImages = false;
  var printerCSS = "replacement-print.css";

By setting doNotPrintImages to true, and printerCSS to the URL of the print style sheet we just created, the script will automatically insert the appropriate CSS <link> into our document’s <head> tag.

Flicker Free#section5

Because our script can’t begin replacing elements until after the entire document has loaded, there will often be a quick flash of unstyled content as the browser waits for the replacement process to begin. This is less of a problem than it is a minor annoyance, but since it’s avoidable we might as well fix it. With the help of another small style sheet, we can do just that.

Before the document’s body begins loading we can dynamically insert a style sheet that will hide these elements entirely. Since linked CSS files are applied even as the document is rendering, no content will be visible during this period. Once our replacement technique is finished executing we can disable this style sheet and our newly stylized headings will be visible once more.

For example, if your page was set up to replace <h2> tags, the following style sheet (available here) would hide them until our replacement technique was finished:

  h2 {
    visibility: hidden;
  }

There is a slight problem with this approach, however. Our entire technique depends on the loading of a test picture to indicate whether the browser supports images. If the image never loads, our technique will never activate. And if our technique never activates, the style sheet that hides our unstyled headings will never be deactivated. Because of this, visitors who have disabled image support in their browsers, but who are still capable of using JavaScript and CSS, will see nothing but empty space where our headings should have been.

We’ll do our part to improve this poor minority’s already-difficult browsing experience by adding a short timeout value to the script. If the test image hasn’t been successfully loaded after one or two seconds (or however long you see fit), the script will automatically disable this style sheet, and the headings will reappear. Those one or two seconds are a slight inconvenience for this exceptionally rare person, but it solves the flicker problem for the other 99.99% of our visitors. What’s important is that we maintain accessibility for everyone.

To enable this optional customization, and to remove the brief flash of unstyled content, you must edit three lines in the JavaScript source:

  var hideFlicker = false;
  var hideFlickerCSS = "replacement-screen.css";
  var hideFlickerTimeout = 1000;

Set hideFlicker = true, and hideFlickerCSS to the URL of the CSS file that you just created to hide your headers.

hideFlickerTimeout should be set to the maximum number of milliseconds (i.e. 1/1000 seconds) that the script will let pass before disabling that style sheet.

Notes and Suggestions#section6

Older versions of Mozilla, including Netscape 6.2, contained a bug where the browser would download images even if the user had instructed it not to display them. This obviously made no sense, and has been fixed since version 1.4. Although this technique will normally work without any problems in these browsers, it will incorrectly diagnose image support and fail when visitors using these browsers have images disabled. I don’t consider this overwhelmingly rare occurrence to be a serious drawback, but it’s worth noting for completeness. There is currently no workaround for this problem.

Use this technique with a translator service, like Google or Altavista’s Babelfish. As long as your font supports the foreign character set, the dynamic images will be translated as well.

The text that you replace does not have to be inside of a heading tag (<h1>, <h2>, etc.); it can be any element on the page. With some fairly simple adjustments, and some manipulation of float values, this technique could produce dynamic drop-caps for any paragraph you apply it to.

You can also replace <a> tags, giving your page stylized hyperlinks, although getting rollovers to work would require more customization.

Instead of replacing content with dynamically generated <img> tags, this technique could avoid using PHP altogether and instead insert dynamic Flash animations.

Acknowledgements#section7

Peter-Paul Koch, for his JavaScript Image Replacement technique.

Simon Willison, for this getElementsBySelector function.

Stuart Langridge, for unobtrusive JavaScript techniques.

About the Author

Stewart Rosenberger

Stewart isn’t going to use this space to plug a website you’ll never visit. He’s a computer technician at the University of Massachusetts. If you say “please,” he’ll probably hold your place in line or feed your pet rock while you’re on vacation. He’s a really nice guy and he likes animals; he doesn’t think you should eat them anymore.

281 Reader Comments

  1. I checked and I left my MIME type as image/png.

    Again, I appreciate your help with any suggestions that might break through this.

    RegEx (or anyone else), I would be happy to email my files if that might enable you to see directly what might be amiss.

  2. Hey guys, do you mind addressing questions about implementation of DTR? A wonk at b2evo suggested I give it a whirl because I want to display text in an Ancient Greek font complete with accents and other unusual diacritical marks. So I followed the instructions and it ALMOST works–the swapout happens, and I get a PERFECT “alt” text, but the image doesn’t load–just an image placeholder, with a little bar for every character that should be there. Any thoughts?

    Thanks.

    mikeyboy

  3. Ok, I hate not being able to figure things out for myself, so I read the article again–my server probably lacks support fot the GD graphics library. I don’t yet even really know what that means–I’ve got their website open in another browser window right now to try to figure it out, and then I will go to my hosting service (the much-maligned Hostdime) and see what they have to say about GD graphics library support).

    Think I’m on the right track?

    Thanks again.

    mikeyboy

  4. I really liked this article and have found it really usefull. One problem I have found when putting it into practice is that the word spacing can not be controlled. Does anyone know how this could be done?

  5. Sorry for being rather remedial, but can anyone help with the syntax of using a class in the replaceSelector(“span”,”fontconvert.php”,true);
    line, i.e. if I want to replace only span class=”fancy” or something. Thanks.

  6. Use CSS selectors. For a span with class “fancy” use:

    replaceSelector(“span .fancy”,”fontconvert.php”,true);

    There’s a space between span and .fancy. I reccommend Eric Meyer’s “Cascading Style Sheets: the definitive guide” to you for learning about CSS. It’s a great reference on CSS 1 and 2.

  7. Thanks very much for the syntax help. I have the book you recommended, though more often I use Meyer’s CSS 2.0 Programmer’s Reference.

    Thanks again.

  8. I keep getting this error

    “Error: No Text Specified”

    I have the

    tags in my document to be replaced but it wont work. What am i missing?

  9. I found I had to write it withOUT the space for it to work.

    replaceSelector(“span.fancy”,”fontconvert.php”,true);

  10. This is a very interesting notion from my point of view, I am building a web site on obscure Fonts. I have tried to follow your (great) tutorial as closely as possible, but it didn’t work for me. Here is the page

    http://www.osop.org/textrep.htm

    Can any one tell from here whats going wrong???

    Thanks in advance John Maillard

  11. I really like this, and have used it on a site of mine…had one problem though:

    When I changed the text-size in the .php file and refreshed in IE, the image stayed the same size and the font went all pixely. This doesn’t happen in Firefox. This continued to happen after I deleted all my temporary internet files etc.

    Anyone else encounter this, or know whats happening?

  12. Great script. Discovered it only yesterday.
    First time I did something with PHP. Well.. did something… copied it and used other values. That’s it.
    It workes and I’m impressed.

    I had the idea to copy the fonts to a directory above root like common with databases. You don’t want to distribute your fonts. You just want to use them. So you don’t want the visitors to download them.

    For example you have the following 3 direcories on your ftp site.
    database – well it says it all
    www – the root directory of the website, here are the htm’s, asp’s, php’s and everything
    fonts – the font directory, available for the scripts but not for visitors of the site

    Is it easy to adjust the php script to use that dir? Should be, I guess, but I don’t know anything about php yet.

    And is are there scripts online with all improvements discussed before included?
    I mean the security issues and easy maintanance improvements like just one php script instead of one for every variation.

    Marcel

  13. I managed to get a simple demo working in less than an hour. Pretty good for someone like me who’s just a hack at JavaScript and knows slightly more than zero about PHP.

    http://darrenwilson.com/test_tube/dynamic_text/

    I don’t know if it’s my font selection (I just used some freeware TTF fonts i had on my machine… most of my fonts are PostScript) but i’m experiencing some odd word spacing problems. Is this due to font selection or some other issue?

  14. After reading some of the previous discussion, i confirmed that the problem i’m experiencing with word spacing issues is entirely related to having word wrapping set to “true.” Turning off word wrapping makes everything space nicely.

    Has anybody come up with a solution that will allow word wrapping but preserve proper word spacing?

  15. Hi Stewart,
    I really appreciate your downtime – your earned it for that article alone >;o)

    Anyway I need help on this promising solution, maybe somebody else could have a look at my files.

    This is the problem:
    To call the script directly with
    http://www.crimeZZZ.net/demo/h1.php?text=eliZZZa
    works fine and produces the cache image.
    Called via the test page
    http://www.crimeZZZ.net/demo/demo.htm
    does not show anything and does not generate a cached image.
    The rest of the files named as in Stewart´s example.

    Up to now I checked the following:
    * Requested support from my provider – everything okay from his point
    * Tested in various browsers – same result

    I really don´t see the error – what am I missing???

    Great thanks for any help
    eliZZZa from Austria

  16. Hello Stewart, your script and your idea are absolutely awesome! A great way to make use of PHP’s powerful features.

    Unfortunately I was unable to make it work and since this (posting a comment here) seems to be the only way to get some kind of support (hopefully you’ll have a support forum at your new site) I thought I’d give it a try because I would really like to use it.

    I am using FireFox (Mozilla 1.7.3). My Apache server has PHP version 4.3.8 with GD enabled (it says version: bundled 2.0.23 compatible) and PNG support enabled. So I think I meet the requirements, don’t I?

    As far as settings I can’t see how I could have done anything wrong…

    I have looked at your on-line demo and copied your settings as a last try but I still don’t get anything.
    I have copied the test.png file to the same folder where the other files are and I have copied your entire replacement.js file to make sure it’s all the same as in your demo. I have named my PHP file h2.php and inside it I have set the path of the font to: ‘/home/username/domains/chaindlk.com/public_html/sources/text-replacement/JaggedDreams.ttf’
    (in this URL I have replaced my username with the word “username” for privacy). I know for sure that this is the correct path because:
    1. I have looked it up in the server’s documentation
    2. I have this path on a CGI file I am using and it works
    3. if I change the path your script gives me a “font missing” error, but with this path I simply don’t get anything at all

    I have also tried using absolute URLs (even though the articles instructs to use the path and not the url).

    As for fonts I have tried 5 different ones to make sure it wasn’t an issue with the font.
    I have tried:
    prefix.ttf
    lydianv.ttf
    JaggedDreams.ttf
    and then I thought of trying the OTF ones and went to http://freefonts.fateback.com where I have downloaded:
    JaggedDreams.otf
    JungleBurnout.otf

    Basically I don’t know what else to try.
    I have troubleshooted this as good as I could.

    Here is the link:
    http://www.chaindlk.com/sources/text-replacement/h2.php?text=test

    What else can I try?

    Also, if I ever get it to work (hopefully somebody will help me here) I have one question:
    is it safe to assume that on an apache server I can set $cache_folder to ‘var’ because (as far as I have read) that is Apache’s temporary files directory. If this is not correct and I leave it set to ‘cache’, would I have to create the directory inside the folder where all the files reside or would I need to create it in the root and give it a path (or an url)? Also would I need to chmod it to 777?

    I hope somebody will be able to answer my questions and make this gorgeous script work for me as well!

    Thanks

  17. I think I have figured out that this will NOT work unless the server has the TTF library support (FreeType library from freetype.org). Is this correct? Can somebody let me know? Thanks

  18. works great, but i have one big problem. when i work with text in tables the m$ internet explorer doesn´t makes a word warp.

    in firefox the word warp works, but there the script has problems with image generated numbers like “6.”

    outside of any tables, the word warp works also in ms ie…

    for example:
    http://www.joerg-wikinger.de/example.html

  19. The test.png was uploaded in wrong format (ASCII instead of binary) – that´s why it did not work.

    I am delighted by your solution!!!
    eliZZZa

  20. Trust me I understand, I use php and have for years so I talk till I’m blue in the face about how great it is, however the flipside to this coin is some companies are so M$ brainwashed that anything but asp or likewise technologies are shunned no matter how many advantages you can prove with your favorite language. I’m a php programmer in the heart, but alas, my shop forces me to write asp to earn the paycheck…

  21. discovering this technique has really saved me a lot of time. I have been playing with it a bit today and am trying to find out if anyone has either of these two problems.
    1. I have several different span classes set to trigger the text replacement.
    for example-
    span.heading
    span.big
    etc.
    when I have checked the results in ie6 it only changes the first span.class it sees and the rest of them just come up as the normally formatted span.
    and
    2. in netscape 6 it does recognise every span.class listed and change to the new font but when the normally formatted span is viewed without the php/js links, it comes up with no space at all between words.
    the site in question is viewable at
    http://www.thepeartreeofbrookline.com
    thanks.

  22. Please help me get past “Error: The server could not create this heading image.”

    First off, here is the phpinfo:
    http://www.hai.org/imagereplace/info.php
    It seems to have all the FreeType support, GD version, etc. that is required.

    I am definitely passing the test of finding the font, but, since others who have had this problem have suggested it, I tried the following variations for $font_file:

    1. absolute address
    2. All the variations of document root addressing that have been mentioned in this discussion so far.

    The ONLY changes I have to the heading.php that Stuart provided is the name/location of the font and a font size of 36.

    I have traced the error to occurring with the call to @ImageTTFBBox but the discussions of that here have provided no answers. Stuart? Someone, please help. I have spent way too much time on this without finding a solution.

  23. first i want say that this technique is great.

    i have a question about this:
    Stewart Rosenberger says at the the end of the article »… although getting rollovers to work would require more customization.«

    ist here any way to do rollovers? for examble to change the fontcolor on hover.

    would be perfect!

  24. Problem with this approach is that you can only use ttf fonts, not adobe fonts (I know this for a fact with ASP.NET and C#, and from what Ive gathered it is hard if not impossible to try to convert them to ttf; the software for that stuff is supposedely really expensive).

    I dont know about you guys, but all my fonts are adobe fonts.. You can use a similar approach with flash if you need more than just ttf fonts..

    The cool thing about using flash is that the swf is loaded once, then it can be used for your entire site since its cached.. The bad thing is that support for flash is sometimes questionable.. I go around this by just showing a crappy arial/tahoma/verdana H1-H6 heading whenever the user doesnt have it, and it works out fine.

    Check this link out… this is where I first saw it.

    http://sports.espn.go.com/nhl/news/story?id=1715637&partnersite=espn

    Here, look at the headline, its all in flash and the headline gets passed in as a querystring in the object/embedd tag.

  25. Works like a charm but when navigating with browser buttons, dtr seems to replicate with no way of stopping… only on mac versions of IE 5.0+
    Anyone else getting/seeing this bug?

    Thanks.

  26. Sir, i just want a help from you that, could we make a text written on uppper side and lower side of image dynamically. If we insert a text from a textfiled called Upper side and other text from a textfield called “Lower side”. I want that the text enter in Upper side textfiled goes automatically at the upper side of image and same for lower side textfield , could it be possible from Javascript.

    Please look an example given at Url:
    http://www.ringcompany.com/designcenter.html

    Click on the right side image. Apopup will open.

    Insert username=amitk
    Password=weblink

    Click on saved design.

    Then click on the link “new1_25_11_04” provided below the link of “Design Name”

    This will show you the details of product. Click on next step.

    Click on Edit.

    This is the example of what i am taling about.

    Enter any text in Upper: or Lower textfields.

    This will show you on image at runtime.

    Please tell me that it is possible in PHP or Possible in Javascript.

    Early reply will be appreciated.

    I trust your site very much.

    Please reply soon.

  27. This caught my eye.

    After trying Shaun Inman’s IFR and injuring myself by the number of problems and lack of support, this script looks promising.

    But a few questions:
    1- How would I be able to get an image to occur in the output?
    (eg: a dotted line automatically to be inserted underneath the outputted image text)

    2- Can you change the case?
    (eg: Original text would be normal case, but final image should all be uppercase)

    If these problems can be ironed out then it would be a perfect script.

    Support welcome, Matthew.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career