Fancy Drop Cap – Part 1

read 21 comments

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():

JavaScript:
  1. $(document).ready(function() {
  2.   swap_letter();
  3. });
  4. function swap_letter() {
  5.   //all the code goes here
  6. }

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:

JavaScript:
  1. $('<img />') //creates the DOM element
  2.   //next line adds the "src" attribute to the <img /> tag
  3.  .src('/images/alphabet/' + first_letter.toLowerCase() + '.gif')

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:

JavaScript:
  1. .attr('alt',first_letter)
  2. .addClass('fancy-letter')

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):

JavaScript:
  1. .prependTo( first_paragraph ); //Note the semicolon here

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

JavaScript:
  1. $('<img />')
  2.   .src('/images/alphabet/' + first_letter.toLowerCase() + '.gif')
  3.   .attr('alt',first_letter)
  4.   .addClass('fancy-letter')
  5.   .prependTo( first_paragraph );

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:

JavaScript:
  1. var first_paragraph = $('#main-content p').get(0);

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

JavaScript:
  1. var first_paragraph = $('#main-content p')[0];

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:

JavaScript:
  1. var first_paragraph = $('#main-content p')[0];
  2. if ( !first_paragraph ) return false;

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:

JavaScript:
  1. var text = first_paragraph.firstChild.nodeValue;
  2. var first_letter = text.substr(0,1);

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:

JavaScript:
  1. first_paragraph.firstChild.nodeValue = text.slice(1);

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:

JavaScript:
  1. $(document).ready(function(){
  2.   swap_letter();    
  3. });
  4. function swap_letter() {
  5.   var first_paragraph = $('#main-content p')[0];
  6.   if (!first_paragraph) return false;
  7.   var text = first_paragraph.firstChild.nodeValue;
  8.   var first_letter = text.substr(0,1);
  9.   if ( text ) {
  10.     first_paragraph.firstChild.nodeValue = text.slice(1);
  11.   $('<img />')
  12.     .src('http://www.englishrules.com/images/alphabet/' + first_letter.toLowerCase() + '.gif')
  13.     .attr('alt',first_letter)
  14.     .addClass('fancy-letter')
  15.     .prependTo( first_paragraph );
  16.   }
  17. }

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.



comment feed

21 comments

  1. Walts

    In Opera 9.01 with javascript on, the letter 'e' after the H enlarges.

  2. Thanks for the info, Walts! I hadn't checked in that browser. It looks like the problem is with some CSS that I added to #drop-down:first-child:first-letter. Firefox applies that formatting to the initial "H" but then the Javascript hides it. I'm removing that style declaration until I figure out how to make it more cross-browser friendly. Take a look again and let me know if you still see problems. Thanks again.

  3. Fil

    Very nice. You'll need to make sure that the first letter is indeed a letter (and not a weird character that would give a link to a non-existant image). And, if you want to make it perfect, you need to deal with paragraphs that start with, e.g, a double-quote, by treating them differently.

  4. Thanks, Fil! You're absolutely right. I have the loop for textNode covered in part 2 (coming soon), but I hadn't thought of the weird character possibility. Maybe I'll throw in a simple regex for [a-zA-Z] or something.

  5. How could you apply this to the first letter of every paragraph within a containing div?

  6. Hi Derek,
    You would simply change the first_paragraph variable from $('#main-content p')[0] to $('#main-content p').get(). You might also want to change the variable name to make it more accurate.

  7. Unfortunately that broke it completely and nothing happens anymore.

  8. After finally deciding to work on the problem myself, I came up with the following to iterate through all the paragraphs of a given element and replace the first letter:

    $(document).ready(function(){
    swap_letter();
    });
    function swap_letter() {
    var paragraphs = $('#content p').get();
    $.each(
    paragraphs,
    function(i) {
    var paragraph = $('#content p')[i];
    var text = $(paragraph).html();
    var first_letter = text.substr(0,1);
    var match = /[a-zA-Z]/.test(first_letter);
    if (match) {
    $(paragraph).html(text.slice(1));
    $('')
    .src('/_images/letters/' + first_letter.toLowerCase() + '.gif')
    .attr('alt',first_letter)
    .addClass('fancy-letter')
    .prependTo(paragraph);
    }
    }
    );
    }

  9. Hi Derek,
    So sorry to give you bad info! That's what I get for trying to reply while on vacation and before testing the code myself. I'm glad you were able to work it out. Good job!

  10. Quote:How could you apply this to the first letter of every paragraph within a containing div?

    Anyone else having trouble getting this to work?
    I've followed both tips above by Karl. None work for me.
    :(

  11. Hi there jammodotnet,
    It looked like Derek had found a solution that worked for him, so I kind of dropped it. I'll take another look, though, and see if I can put something together for you. Look for another comment (or maybe even an entry) from me before the weekend is over.

  12. The code below is simpler, more robust and thus better.
    jQuery is not a panacea to be used always and everywhere. Otherwise pretty
    good library.

    $(document).ready(function(){
    var ps = document.getElementsByTagName("P") ;
    for ( var j = 0 ; j < ps.length ; j++ )
    {
    swap_first_letter( ps(j) );
    }
    });
    function swap_first_letter( first_paragraph, first_letter )
    {
    if ( !first_paragraph ) return false;

    var text = first_paragraph.firstChild.nodeValue;
    if ( ! text ) return false ;


    var first_letter = text.substr(0,1);

    first_paragraph.firstChild.nodeValue = text.slice(1);

    var jq = $('<img />') ;
    jq.attr("src", ('alphabet/' + first_letter.toLowerCase() +
    '.gif') ) ;
    jq.attr('alt',first_letter) ;
    jq.addClass('fancy-letter') ;
    jq.prependTo( first_paragraph ) ;
    }

  13. For my solution to adding fancy drop caps to every paragraph within a certain DIV, see my other entry: Multiple Fancy Drop Caps.

  14. Karl, great articles! Though I have not used anything yet and truth be told this is the first one I have read, I find it just wonderful that you take the time to reply to the comments. This gives your site an 'A++' in my book.

    Keep up the great work!

    -mpare
    http://www.paretech.com

  15. Thank you, Matt! It means a lot to me that people like you appreciate what I'm doing here. Makes it all worthwhile. Come back and explore the other entries any time!

  16. etnt

    Hi,

    Thanx for making this excellent site!
    I was just wondering how you turned the letters into images ?

  17. Hi, etnt, you're welcome. So glad you like it.

    For turning the letters into images, I used the Type tool in Photoshop (though you can use just about any image editor with a Type function). I sized the canvas to be a bit larger than a single letter, typed a letter, and cropped the image so there was no white space around it. I saved the letter as a gif and then repeated the process. By the end, I had an image for each letter of the alphabet—a.gif, b.gif, c.gif, and so on.

  18. So so nice jQury Article , i waiting for part two. Thanks

  19. No need to wait. Part 2 has been here for quite some time.

  20. brandon

    I cannot get the letter to show up in front of the paragraph. I know the jQuery code is working somewhat, because it is slicing away the first character, it's just not inserting the image tag. I've copied and pasted the snippet, so I know there are no typos. Markup is also valid

    jQuery

    Lorem ipsum dolor sit amet, consectetur adipiscing elit.

    Morbi placerat sem in diam. Nam ut turpis. Nunc auctor adipiscing felis.

    In sit amet libero id sapien venenatis ultricies. Proin aliquam.

    Lorem ipsum dolor sit amet, consectetur adipiscing elit.

    Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

9 Pings

  1. Fancy Drop Cap - Part 1...

    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 thou...

  2. [...] easy with JQuery 15. Using JQuery to modify presentation while preserving document semantics 16. Fancy Drop Cap 17. Easy Multi Select Transfer with jQuery 18. Javascript Tooltips on Steroids 19. Edit In Place [...]

Sorry, but comments for this entry are now closed.


Advertise on our network
See our other websites