Automatic pullquotes with JavaScript and CSS

As you may have noticed, there are now pullquotes in some articles on this site. I think they can be a nice design element and a good way of highlighting important phrases. As with most things there are several ways of creating pullquotes, the most straightforward being copying the text you want and paste it into a blockquote element. I didn't want to do that for several reasons.

The main reason is the amount of work involved. I could go through my older articles to add pullquotes, but what if I should decide that I no longer want pullquotes? I'd have to go through every article again to remove the quotes. Lots of work. I wanted a way to add pullquotes without having to duplicate text in the markup, and came up with a JavaScript based technique.

Using JavaScript for this is fine with me since the pullquotes serve mostly as scannability aids and design elements. The text exists in the markup, so users with JavaScript off aren't missing out on any actual content. If JavaScript is on but CSS is off, the pullquotes are still created but without any other styling than the browser's default blockquote styling. That too is an acceptable fallback.

With the degradation discussion out of the way, it's time to explain how the pullquotes are created. To turn a piece of text into a pullquote, all I need to do is wrap the text that will become a pullquote in a span element and give it the class name "pullquote". An example:

  1. <p>To turn a piece of text into a pullquote, all I need to do is <span class="pullquote">wrap the text that will become a pullquote in a span element and give it the class name "pullquote"</span>.</p>

The JavaScript will turn that into this:

  1. <blockquote class="pullquote"><p>wrap the text that will become a pullquote in a span element and give it the class name "pullquote"</p></blockquote>
  2. <p>To turn a piece of text into a pullquote, all I need to do is <span class="pullquote">wrap the text that will become a pullquote in a span element and give it the class name "pullquote"</span>.</p>

Here's the JavaScript, with explanatory comments (just copy and paste this into a file):

  1. var pullquote = {
  2. init : function() {
  3. // Check that the browser supports the methods used
  4. if (!document.getElementById || !document.createElement || !document.appendChild) return false;
  5. var oElement, oPullquote, oPullquoteP, oQuoteContent, i, j;
  6. // Find all span elements with a class name of pullquote
  7. var arrElements = document.getElementsByTagName('span');
  8. var oRegExp = new RegExp("(^|\\s)pullquote(\\s|$)");
  9. for (i = 0; i < arrElements.length; i++) {
  10. // Save the current element
  11. oElement = arrElements[i];
  12. if (oRegExp.test(oElement.className)) {
  13. // Create the blockquote and p elements
  14. oPullquote = document.createElement('blockquote');
  15. oPullquote.className = oElement.className;
  16. oPullquoteP = document.createElement('p');
  17. // Insert the pullquote text
  18. for(j = 0; j < oElement.childNodes.length; j++) {
  19. oPullquoteP.appendChild( oElement.childNodes[j].cloneNode(true) );
  20. }
  21. oPullquote.appendChild(oPullquoteP);
  22. // Insert the blockquote element before the span element's parent element
  23. oElement.parentNode.parentNode.insertBefore( oPullquote,oElement.parentNode );
  24. }
  25. }
  26. }
  27. };
  28. // addEvent function from http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
  29. function addEvent(obj, type, fn) {
  30. if (obj.addEventListener)
  31. obj.addEventListener( type, fn, false );
  32. else if (obj.attachEvent)
  33. {
  34. obj["e"+type+fn] = fn;
  35. obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
  36. obj.attachEvent( "on"+type, obj[type+fn] );
  37. }
  38. }
  39. addEvent(window, 'load',pullquote.init);

If you are already using an addEvent() function, use that and remove the one that is included with this script.

Yes, I'm using innerHTML, but only because I couldn't find a nice DOM based function that does the job of copying the contents of the pullquote span, including any child elements. Using cloneNode copies the span element as well, and I don't want that. If anyone is aware of a DOM replacement for innerHTML that does what I want, please let me know. If not, maybe it's about time for someone to create one.

Update (2006-09-19): The script now uses the DOM instead. I based my change on the snippets provided by several people (Dan, Lachlan, Rowan). The loop that is needed is much simpler than I thought it would be when I first created the script. I guess my brain was in its overcomplicating mode.

The script does make a couple of assumptions about the markup that you should be aware of. First, it inserts the blockquote element before the span element's parent element without first checking what type of element it is. This will lead to invalid markup being created if you add a pullquote span inside an inline element or a list item, for example. Second, it uses document.createElement, which will not work in documents served as application/xhtml+xml. If you need it to work in that situation, Simon Willison describes a solution in Javascript, the DOM and application/xhtml.

With the necessary HTML in place, the pullquote needs a bit of styling. If you want to be able to decide on a quote-by-quote basis whether it should be floated left or right, you could add an extra class name. I decided to float the pullquotes left by default, and that any pullquotes that also have the class name "alt" should be floated right:

  1. blockquote.pullquote {
  2. float:left;
  3. width:10em;
  4. margin:0.25em 0.75em 0.25em 0;
  5. padding:0.5em;
  6. border:3px double #ccc;
  7. border-width:3px 0;
  8. color:#333;
  9. background:transparent;
  10. font:italic 1.3em/1.3 Georgia;
  11. }
  12. blockquote.alt {
  13. float:right;
  14. margin:0.25em 0 0.25em 0.75em;
  15. }
  16. .pullquote p {
  17. margin:0;
  18. text-align:center;
  19. }
  20. .pullquote p:first-letter {text-transform:uppercase}

And there you go. Automatic pullquotes with a minimum amount of extra markup.

Update (2006-10-21): There are at least two people who have created Wordpress plugins that use similar scripts. Viper007Bond's plugin is described in Javascript Pullquotes and Stephen Rider's in Wordpress Plugin: Javascript Pull-Quotes.

Posted on September 18, 2006 in JavaScript, CSS