case study

Hot and graceful jQuery dropcaps

Dropcaps are a staple of magazine and newspaper design, but aren’t always the easiest to implement on the web. Using this little jQuery and CSS tutorial, you can easily add hot little dropcaps to your web articles without bothering your developers. This method will also let you sleep at night knowing your caps will degrade gracefully, displaying just fine in browsers without CSS or JavaScript enabled.

Please note, this tutorial is for those who are familiar with jQuery and CSS. No basics taught here. Also nerd alert.

Here we have a flippin’ sweet dropcap on an article page for the new Combating Pornography website we’re building (launching early 2009).

Tadd recently noted that he’s been using some nice CSS 2 selectors in the new General Conference design. In his example, he’s styling the initial character of the article with CSS typography. Noble indeed.

However, I am more of a type snob and wanted my dropcap to be set in Caecilia, matching some other type found throughout this new site.

(Yes, this means saving 26 images of dropcaps in Photoshop. Luckily this took me all of 10 mins. As for translated sites, more on that below.)

So our task is to style the initial character with a background image, with hand-adjustable pixel spacing around each individual letter, and in a way that degrades as gracefully as Audrey Hepburn if she were a nerded-out website.

I also don’t want to bother the developers (who read this blog – hi Mindie!) to put a <span> around the initial character, and I really don’t want to beg them to throw a different class on that span depending on which letter of the alphabet that character is.

Sound like a daunting task? It’s not.

HTML it up

First let’s make sure the HTML is set up nice and easy:

Here we have the initial paragraph from Michael Gardner’s article on Hope, Healing, and Dealing with Addiction (here is the full comp so you have some context). No spans, no special classes. Easy fo’sheezy on the developers.

jQuery action

Next we hook up our page with some jQuery, and start the show.

<head>

<script src="scripts/jquery.js" type="text/javascript"></script>

<script type="text/javascript" charset="utf-8">
$(document).ready(function(){ 

});
</script>
</head>

Inside our jQuery block, we’ll first grab the first paragraph in our article (in my case, inside my “body” div), and add a class of “dropcap” so we can remove the indent and refer to it later:

$(document).ready(function(){ 
    p1= $("#body p:first");
    p1.addClass("dropcap");
});

Next, we need to grab the text from the first paragraph. We do this by taking the actual paragraph element from our p1 object, and asking it for its content using a standard JavaScript function, “textContent.” We also want to strip out any leading spaces or quotes, using some regular expression magic:

$(document).ready(function(){ 
    p1= $("#body p:first");
    p1.addClass("dropcap");
    p= p1[0].textContent.replace(/^\s+/, "").replace(/^"/, "").replace(/^'/, "");
});

Next we need the initial character from that paragraph (whic we know is alpha-numeric), using the standard function “charAt.”

$(document).ready(function(){ 
    p1= $("#body p:first");
    p1.addClass("dropcap");
    p= p1[0].textContent.replace(/^\s+/, "").replace(/^"/, "").replace(/^'/, "");
    c= p.charAt(0);
});

Now comes the money. In one statement, we will replace the html inside our jQuery object (p1) with a new block of code that wraps the initial character in a span, adds a class of “dropcap-” + the initial letter (don’t forget the lowercase), and adds a style with a background image for that specific letter. Check it.

$(document).ready(function(){ 
    p1= $("#body p:first");
    p1.addClass("dropcap");
    p= p1[0].textContent.replace(/^\s+/, "").replace(/^"/, "").replace(/^'/, "");
    c= p.charAt(0);
    p1.html('<span class="dropcap-' 
        + c.toLowerCase() 
        + '" style="background-image: url(images/drop-' 
        + c.toLowerCase() 
        + '.jpg)">' 
        + c 
        + '</span>' 
        + p.substring(1, p.length));
});

Beautiful. The resulting HTML:

CSS magic

The code above adds a specific class and (gasp!) an element-level style for one reason: keep it simple for the developers (and me).

By giving each letter its own unique class, I can adjust the pixel padding as I see fit. By defining the background image on the element level, I don’t need dozens of lines of code in my CSS file to define each letter class and each letter background. jQuery does it for me.

In our CSS file, I have the following block of code to manage the paragraph:

/* dropcaps */
#body .dropcap{
    text-indent: 0;
}
.dropcap-a, 
.dropcap-b, 
.dropcap-c, 
.dropcap-d, 
.dropcap-e, 
.dropcap-f, 
.dropcap-g, 
.dropcap-h, 
.dropcap-i, 
.dropcap-j, 
.dropcap-k, 
.dropcap-l, 
.dropcap-m, 
.dropcap-n, 
.dropcap-o, 
.dropcap-p, 
.dropcap-q, 
.dropcap-r, 
.dropcap-s, 
.dropcap-t, 
.dropcap-u, 
.dropcap-v, 
.dropcap-w, 
.dropcap-x, 
.dropcap-y, 
.dropcap-z {
    width: 75px;
    height: 80px;
    display: block;
    text-indent: -5000px;
    float: left;
    margin-top: 5px;
    background: transparent top center no-repeat;
}

.dropcap-a{ width: 95px;}
.dropcap-d{ width: 85px;}
.dropcap-h{ width: 85px;}
.dropcap-i{ width: 56px;}
.dropcap-j{ width: 56px; height: 85px;}
.dropcap-k{ width: 85px;}
.dropcap-m{ width: 100px;}
.dropcap-n{ width: 85px;}
.dropcap-q{ width: 85px; height: 90px;}
.dropcap-u{ width: 85px;}
.dropcap-v{ width: 85px;}
.dropcap-w{ width: 120px;}
.dropcap-x{ width: 85px;}
.dropcap-y{ width: 85px;}

I’ve removed the text-indent on this first paragraph, and defined the basic styles for each dropcap, including custom pixel widths for a few.

This method ensures that for anything not defined, like a Chinese or Spanish character, the styles won’t apply and you still have a perfectly fine-looking page no matter what your first letter is. It also means that if you were so inclined, you can define styles and images for any character (as long as your server / target browser supports those characters):

Not only is this saving me hassle, this is saving a decent amount of Java (or Ruby or PHP) code for our developers, and enables me to hand over an even more complete prototype.

Big ups to Pete Lasko, who nerded out with me and helped with some of the code.

posted by Jason Lynes on Tuesday, Dec 02, 2008
tagged with code, programming, css, html, jquery, javascript


12 comments

If any fellow nerds out there have any tips on making this javascript even cleaner or more compliant, please leave a comment.

comment by Pete Lasko 6 hours later

This is kind of 6’s either way, but I would put the the drop-cap image url reference in the style-sheet. So your HTML code would be even cleaner.

&lt;p class="dropcap"&gt;&lt;span class="dropcap-1"&gt;I&lt;/span&gt;n my...&lt;/p&gt;

I guess this would also load all 26 letters of the English alphabet, when only one was needed. So maybe I wouldn’t.

comment by Nathan Philpot 7 hours later

Nice writeup. Thanks for sharing the process.

A few tweaks for ya per Pete’s request:

p= p10.textContent.replace(/\s+/, ””).replace(/”/, ””).replace(/’/, ””);

can become

p= p10.textContent.replace(/[\s”’]+/, ””);

This combines all three replace statements into one, and also adds the ”+” option which will replace 1 or more of any of those just in case (still at the start of the string as directed by the ^ char). Quickly tested, hopefully it works in actual code.

Not fully thought out, but you might be able to simplify the CSS a bit by just defining a .dropcap class that has all of the default stuff (saving 26 lines of .dropcap-a, .dropcap-b, etc). Change the span to have both dropcap and dropcap-a. Still define the .dropcap-a { width:95px; } for those that need it and for .dropcap-c in the span it will just fall back to the default since .dropcap-c isn’t defined.

One step further would be to combine all of the same widths in the custom lines. .dropcap-a, .dropcap-d …. { width:85px; } There are 8 with width:85 so removes some duplication (9 if you count q which also defines height).

That’s all I got on an initial pass. All in all pretty picky changes as it’s very clean to begin with. Again, nice job.

I tried something similar at a previous job and we ran into issues of sometimes the first word would be in an anchor (or other HTML element: strong, em, etc). Any potential for that here?

comment by Aaron Barker 8 hours later

doh… just found a potentially major issue. Because you get the text of the paragraph all the HTML elements are removed. When you reinsert the text, any links, formatting etc for the first paragraph will be gone.

Only an issue if you have any of that in your first paragraph, but at least good to note for others who may want to use this.

comment by Aaron Barker 8 hours later

Great feedback yo.

@Nathan – I didn’t put the background images in the style sheet to save myself typing. The jQuery does it for me. Laziness?

@Aaron – your adjusted regular expression won’t work for me, as I need to take out the leading spaces, AND then quotes. If the paragraph somehow starts with a space and then a quote, your regex won’t catch that.

Also @Aaron, your CSS suggestion of putting a .dropcap class on the span won’t work either (I tried this), as any character you don’t have defined, such as a foreign character you don’t have a dropcap for, will still indent the text and float, which I don’t want if I don’t have a dropcap for it.

And lastly, @Aaron does indeed find the killer flaw, in that the HTML is stripped from the first paragraph. I’ll need to fix this. All – is there a simpler way of grabbing the first non-HTML, non-punctuation character and wrapping a span around it? Tricky.

comment by Jason Lynes 10 hours later

Chris pointed out that my dropcaps were a bit short, so I’ve resized ‘em a bit to fill 4 lines of text at this normal size. A bit sharper.

comment by Jason Lynes 12 hours later

This is pretty small, but your document.ready statement can be a little more concise:

$(function(){

Word.

});

Good work.

comment by Clifton Labrum 14 hours later

Picking nits here, as this looks like a neat implementation of both jQuery and CSS. But wouldn’t it make sense to optimize this by using CSS sprites, and having a single “alphabet” graphic? You’d need to monkey with the vertical offset, so maybe it wouldn’t be worth it. Something to think about for “future iterations”, though?

comment by Charlie Park 16 hours later

To fix the problem of losing HTML formatting in the first paragraph, you could use the jQuery html() function instead of the “textContent” property. I believe this would just be a simple change to your existing markup.

comment by John Dilworth about a day later

@John I initially tried the .html() function, and couldn’t get it to work. I agree that .textContent is maybe not the best way to go, but it was working for the simplest case. I’ll give .html() another go, and see if I can’t figure out what my problem was.

comment by Pete Lasko 2 days later

@pete I originally made a script like this for my site over a year ago, but later decided that I didn’t necessarily want a drop cap on every page, so I opted for hardcoding the classes.

I obviously didn’t think through all the details, but here is what my code looked like using the .html() property. No consideration for html tags, or characters other than letters in the first position.

function dropCap(el){ var str = ( $(el).html()); var firstletter = str.substr(0,1); var replaced = str.replace( firstletter ,”<span class=’ dropcap’>” + firstletter + ”<\/span>”); $(el).html(replaced); }

comment by John Dilworth 2 days later

jQuery: I’m a fan.

comment by American Yak 32 days later

Add your comment

:
:

Required but not shared.

:
:

Use HTML or Textile for formatting. Resize?

Foul language or excessive praise will be moderated.

Comment preview