Fancy Drop Cap – Part 1

Introduction

Last spring when I implemented a new design for my weblog, I wanted to use a fancy drop cap for the first letter of the first paragraph of the first post of each page. There are all sorts of ways to make a drop cap happen, but since I was reading Jeremy Keith's excellent book DOM Scripting at the time, I thought I'd do it that way. The DOM scripting method that I put together had some important benefits for me at the time:

  • It used an image, so I didn't have to worry about installed fonts on users' machines
  • The HTML source stayed intact, so search engines wouldn't trip over a first word with a missing first letter.
  • It degraded nicely, so if users had JavaScript or images or both turned off, everything would still look fine, just a little less pretty.

Now that I'm learning jQuery, I thought I'd revisit my code and see if I could tidy it up a bit, the jQuery way.

Image Set

AI first put together a set of images, one for each letter of the alphabet, using a font from the Dieter Steffman collection at typOasis. If you don't want to go through the laborious process of converting letters into images, you can download mine (20KB zip). See the letter "A" floating to the right of this paragraph for an example.

Setting up the Code

Instead of putting all of the code in a $(document).ready() function, I created a separate function and just called it inside $(document).ready():

[js]$(document).ready(function() { swap_letter(); }); function swap_letter() { //all the code goes here } [/js]

Now we can get down to business.

Insert Image Here

The easiest part of the process was inserting the image, because jQuery makes it almost effortless.

I needed two variables here — one for the first paragraph ( first_paragraph ) of the post and one for the first letter ( first_letter ). I started by adding the image with its "src" attribute to the DOM. All of the image files had this basic naming: /images/alphabet/n.gif, where "n" could be any letter of the alphabet, so you can see how I concatenated the "src" attribute using the file path, a lower-case "first_letter" variable and the image file extension:

[js]$('') //creates the DOM element //next line adds the "src" attribute to the tag .src('/images/alphabet/' + first_letter.toLowerCase() + '.gif') [/js]

Then I gave the image "alt" text in case images are turned off (thanks to Klaus Hartl for reminding me :) ) and a class for CSS styling:

[js] .attr('alt',first_letter) .addClass('fancy-letter') [/js]

By the way, I could have put all four of those lines (minus the comments) on the same line and it would have worked the same, but I thought it would be more readable this way.

Now that the element was created, I wanted to add it to the page, right at the beginning of the first paragraph (hence the "first_paragraph" variable):

[js] .prependTo( first_paragraph ); //Note the semicolon here [/js]

And that's it! The image is prepended to the first paragraph:

[js] $('') .src('/images/alphabet/' + first_letter.toLowerCase() + '.gif') .attr('alt',first_letter) .addClass('fancy-letter') .prependTo( first_paragraph ); [/js]

So, let's back up a bit.

Set the First Paragraph

That bit of code so far isn't going to do anything until we know what first_paragraph and first_letter mean. For my purposes, finding the first paragraph was easy, since I knew it would always be the first paragraph of the DIV with an id of "main-content". We can write this a few different ways, but I wanted to get the DOM element itself, not the jQuery object, so I had to do it either like this:

[js] var first_paragraph = $('#main-content p').get(0); [/js]

or, per John Resig's suggestion, like this:

[js] var first_paragraph = $('#main-content p')[0]; [/js]

I then had to stop the script if a page for some reason didn't have a paragraph inside a "main-content" DIV. Easy enough:

[js] var first_paragraph = $('#main-content p')[0]; if ( !first_paragraph ) return false; [/js]

With that done, I moved on to the first letter.

Set the First Letter

This part was much harder. If I knew that the first letter would never be wrapped in a tag of its own, such as <a href=""> or <strong>, I'd be in good shape. But I didn't know that. I couldn't even be sure that the first letter wouldn't reside in nested tags. (Stay tuned for Part 2, in which I'll show how I tackled that problem.) To make a long tutorial a little less long, though, let's pretend that we can assume the first character of the paragraph will be a letter between a and z. We'll need to get the value of the "text node" of the first paragraph and then grab just the first character of it:

[js] var text = first_paragraph.firstChild.nodeValue; var first_letter = text.substr(0,1); [/js]

Line 1 gets the first child node of the paragraph and then the value of it. Line 2 gets a substring of that value from 0, which is the first character, to (but not including) 1, the second character.

Done! We have our two variables, so the JavaScript will know where to find the first letter image and where to put it. Ah! But there is one more step: We need to remove the first letter of text that the image is supposed to replace.

Drop the First Letter of Text

It's a good thing we have that text variable. We're going to slice that text at the second letter, so that the first letter is cut off:

[js] first_paragraph.firstChild.nodeValue = text.slice(1); [/js]

Now we're done! For real this time! Prepending the image goes after slicing off the first letter. Just to be sure things wouldn't break, I also didn't let the slicing or prepending happen unless text was really there. Here is the final code in its entirety:

[js] $(document).ready(function(){ swap_letter(); }); function swap_letter() { var first_paragraph = $('#main-content p')[0]; if (!first_paragraph) return false; var text = first_paragraph.firstChild.nodeValue; var first_letter = text.substr(0,1); if ( text ) { first_paragraph.firstChild.nodeValue = text.slice(1); $('') .src('http://www.englishrules.com/images/alphabet/' + first_letter.toLowerCase() + '.gif') .attr('alt',first_letter) .addClass('fancy-letter') .prependTo( first_paragraph ); } } [/js]

Demonstration of the Fancy

Here is an example of the fancy drop cap. Lovely "H," no? The code to render this drop cap is the same as the code above, except it references a different ID.

Update: To see how you can add fancy drop caps to every paragraph within a certain DIV, see my other entry: Multiple Fancy Drop Caps.

Update

I've written a Fancy Letter Plugin that does all the hard work for you. You write a line of jQuery like $('div.content p').fancyletter(). The plugin wraps the first letter of the selected elements in a <span> with class names that you can then style to your needs.