Fancy Drop Cap – Part 2

read 12 comments

In Fancy Drop Cap - Part 1, I showed how I used jQuery to insert a drop cap on my personal weblog. But there is still some unfinished business to take care of:

  1. Accounting for cases in which the first paragraph (where I want my drop cap to go) starts with another tag of some sort (<a href="...">, <em>, etc.)
  2. Adding a little CSS to the drop cap

So let's begin with item 1. As you may recall, we defined three variables, first_paragraph, first_letter, and text. The variables allowed us to get the value of the textNode of the first letter of the first paragraph, so we could replace it with the image. Here is what that part looked like:

JavaScript:
  1. var first_paragraph = $('#main-content p')[0];
  2. if (!first_paragraph) return false;
  3. var text = first_paragraph.firstChild.nodeValue;
  4. var first_letter = text.substr(0,1);
  5. if ( text ) {
  6.   first_paragraph.firstChild.nodeValue = text.slice(1);
  7. }

The only problem with that code is that line 3 assumes that the first child node of the first paragraph is actually a text node. But what if it's a span tag (<span>) or a link (<a href="...">)? Well, in that case we'll need to keep drilling down through the nodes until we can't go any farther.

Continue Reading Below

Loop the Loop

To do that, we'll set an intermediate variable, called node, initially making it the same as first_paragraph:

JavaScript:
  1. var node = first_paragraph;

Next, we change our node variable to be defined as the first child of that node (node = node.firstChild), and we keep doing that until there are no more child nodes left, by using a "while" loop:

JavaScript:
  1. while (node.childNodes.length) {
  2.   node = node.firstChild;
  3. }

So, in other words, as long as there is a child node, our variable will be reset as that child node.

The First Letter — and Only a Letter

When that's all done, we set our text variable, this time as the value of our node variable:

JavaScript:
  1. var text = node.nodeValue;

Now all we have to do is get the first letter of the text so that we can replace it with the drop-cap image: var first_letter = text.substr(0,1).

There is just one more thing that we should account for — the possibility that the first character is either the beginning of a self-closing tag such as <img /> or some other character for which we have no image. In my case, I only want a letter "a" through "z" to be replaced. Here is a simple regular expression for that, followed by a "if" condition that wraps the image-replacement code:

JavaScript:
  1. var first_letter = text.substr(0,1);
  2. var match = /[a-zA-Z]/.test(first_letter);
  3. if ( match ) {
  4.   // image-replacement code goes here
  5. }

Line 2 tests if first_letter is a lower-case or upper-case letter. If it is, "match" will return true; if it isn't, "match" will return false. Thanks to Fil for posting a comment about that on Part 1.

The full code

Here is what all of the code looks like.

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 node = first_paragraph;
  8.   while (node.childNodes.length) {
  9.     node = node.firstChild;
  10.   }
  11.   var text = node.nodeValue;
  12.   var first_letter = text.substr(0,1);
  13.   var match = /[a-zA-Z]/.test(first_letter);
  14.   if ( match ) {
  15.     node.nodeValue = text.slice(1);
  16.     $('<img />')
  17.         .attr('src','/images/alphabet/' + first_letter.toLowerCase() + '.gif')
  18.         .attr('alt',first_letter)
  19.         .addClass('fancy-letter')
  20.         .prependTo( first_paragraph );
  21.   }
  22. }

For an explanation of the parts of this code not discussed in this entry, see my previous entry, Fancy Drop Cap - Part 1.

Adding Some Style

If we leave the fancy letter the way that it is, it will stick up above the rest of the first line in the paragraph. But we want it to drop. Fortunately, we've given our image a class called "fancy-letter," so we can just attach the style to that class. Let's float the image left and give it some padding on the top, right, and bottom:

CSS:
  1. img.fancy-letter {
  2.   float: left;
  3.   margin: 2px 2px 2px 0;
  4. }

That's all there is to it! If you'd like to try this on your own site, you can download the script here. You'll also need the jQuery source code, of course. And here is a zip file of the fancy letters that I use. (20KB zip)

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

12 comments

  1. From accessibility point of view, much better is don't put an img tag with alt attribute, but surround the first letter by span tag and add look via CSS.

    How manage these images? Simple by add class related to letter, like this:

    <span class="fancy-letter letter-t">T</span>hat's all there is to it!

  2. Yeah... what Wojciech Bednarski said. I want to wrap the first letter of a specific tag in a span and add a class with a name that has that letter. Does anyone know how to do that in jQuery?

  3. Wojciech and Craig,

    Based on your feedback, I created a Fancy Letter Plugin that wraps a span tag around the first character of all elements matched by the jQuery selector.

  4. Thank you Karl!!!

  5. Nick

    Wouldn't the source just show:

    <p><strong>That's all there is to it!&lt/strong>

    because jquery is applied on the fly? Then if javascript was disabled, it would just show up as a letter formatted like the rest of the paragraph. Seems like the span wouldn't be necessary.

  6. Nick, the issue that Wojciech and Craig were referring to was accessibility when JS is enabled, not graceful degradation. Screen readers that take JS into account would read out the image's alt attribute and then "hat's all there is to it."

  7. Kenneth

    I for the life of me cannot seem to get this script to work... I don't know what I am doing wrong. I have Jquery and your script in place. I have your alphabet files and still... nuthin. What's up??

  8. Hi Kenneth,
    I have no idea what's up. Are you using Firefox with Firebug so you can see what's happening? Are you getting an error message? Do you have a page you can link to so we can see the problem?

  9. Is there any particular reason why Line 13:
    var first_letter = text.substr(0,1);
    Is not:
    var first_letter = text.charAt(0);
    ? Just wondering. Cheers

  10. Hi Dan,
    Nope, no reason at all. charAt will work just as well. Probably a better choice.

  11. molokoloco

    Hi,

    Today this is not work for me in FF3.5...
    When i manipulate DOM, css don't apply..

    
    <style>
    p:first-letter 
    {
    color:#ff0000;
    font-size:xx-large;
    }
    </style>
    

  12. Karthic

    Hi Karl,
    I am using this fancy letter on my aspx page. I am unable to get the code to wrap the first letter of all the paragraph inside the div..I saw the demo of the new jquery Fancy Letter Plugin but still i wasn't able to implement it.
    i am using the below code. I am using your alphabets folder container the images of all the letters. I am able to get the image for the first letter of the first paragraph but i need that for all the paragraph in the div.
    I know the jquery fancy letter plugin wraps all the first letter of paragraph in span tag but i am not able to implement that. Can you send me the sample page using the fancy letter plugin?

    
    
    $(document).ready(function(){
      swap_letter();  
    
        
    });
    
    function swap_letter() {
      var first_paragraph = $('#content')[0];
      if (!first_paragraph) return false;
      var node = first_paragraph;
      while (node.childNodes.length) {
        node = node.firstChild;
      }
    
      var text = node.nodeValue;
      var first_letter = text.substr(0,1);
      var match = /[a-zA-Z]/.test(first_letter);
      if ( match ) {
        node.nodeValue = text.slice(1);
        $('')
            .attr('src','images/alphabet/' + first_letter.toLowerCase() + '.gif')
            .attr('alt',first_letter)
            .addClass('fancy-letter')
            .prependTo( first_paragraph ); 
      }
    }
    
    
    
    
    
    img.fancy-letter {
    
      margin: 2px 2px 2px 0;
    }
    
    
    
    

2 Pings

  1. [...] Google search turned up a tutorial on Learn jQuery which fit my needs. However, being a perfectionist, I felt I could improve on the code and tailor [...]

Sorry, but comments for this entry are now closed.