Site icon MonstersPost

Hidden Click-To-Expand Paragraph Text with jQuery

Dynamically-expanding content is a neat idea which can be tucked into many website designs. Hiding or teasing part of some text may prove useful with a small bio, item description, or even a larger block of text. This may take up the whole page as with my example - or you might consider using many smaller text blocks which can be toggled to view.

Live Demo - Download Source Code

In this tutorial I want to explain how to create a simple expanding paragraph animation effect. The script uses jQuery for the animation which will calculate how much teaser text should be displayed. This is really nice with columns of information that require large amounts of space on the page. But it's also great for dense web copy that can be hidden to reduce scrollbar size. Take a peek at my live demo to see the final result.

Page Setup

The first step is to create a new blank HTML5 page coupled with an external CSS stylesheet. I've included a local copy of jQuery along with an external link to the Font Awesome webfont. You might instead download a local copy but I find it's easier with a CDN hosting the font files.

<meta>
<meta content="text/html">
<meta content="Jake Rocheleau">
<link rel="shortcut icon" href="http://static.tmimgcdn.com/img/favicon.ico">
<link rel="icon" href="http://static.tmimgcdn.com/img/favicon.ico">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css">

Everything in the page body is responsive so it looks nice on any screen size. The container div #wrapper uses a max-width of 900px along with a centered position using margin: 0 auto. At the top I've added a small header along with a photo of Steve Jobs during his Stanford Commencement speech.

The text was copied from this transcript and broken down into paragraphs. This main page content is located in another container with the ID #content. The container isn't necessary but it helps to keep things organized.

<div id="wrapper">
<h1>Steve Jobs Commencement Speech</h1>
<figure><img src="img/steve-jobs-address.jpg" alt="steve jobs stanford 2005 address"></figure><div id="photo"></div>
<div id="content">
<blockquote class="bigtext"><p>I am honored to be with you today at your commencement from one of the finest universities in the world. I never graduated from college. Truth be told, this is the closest I've ever gotten to a college graduation. Today I want to tell you three stories from my life. That's it. No big deal. Just three stories. </p></blockquote>
<p class="expand"><i class="fa fa-arrow-down"></i> Click to Read More <i class="fa fa-arrow-down"></i></p>
<p class="contract hide"><i class="fa fa-arrow-up"></i> Click to Hide <i class="fa fa-arrow-up"></i></p>
</div>
</div>

The paragraphs are contained within a blockquote element using the class .bigtext. However you should be able to use any HTML element as long as the classes matchup to the jQuery code. Take note that directly after the blockquote I've included two separate paragraphs with the classes .expand and .contract.

These elements should be placed right after the blockquote so jQuery knows which elements to target. You do not need a container for these elements, they should only be siblings located at the same hierarchy. It should be kept in this same order so the text appears first, then the .expand paragraph followed by the .contract paragraph.

Inside these paragraph tags you'll notice Font Awesome arrow icons along with some text. The whole paragraph behaves like a link even without any anchor element. So the user will always get a cursor when hovering and both paragraphs will appear clickable.

General Styles with CSS

The larger portion of my CSS is a customized reset built off Eric Meyer's code snippet. I've added extra lines to reset all fonts and enable responsive images, along with some other tidbits. The header text uses a custom Google font named Paytone One and the background texture is rice paper #2 from Subtle Patterns.

@import url('http://fonts.googleapis.com/css?family=Paytone+One');

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
  outline: none;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
html { overflow-y: scroll; }
body {
  background: #f7f5f1 url('../img/ricepaper2.png'); /*http://subtlepatterns.com/rice-paper-2/*/
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 62.5%;
  line-height: 1;
  color: #414141;
  padding: 45px 0px;
}

br { display: block; line-height: 1.6em; } 

article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }
ol, ul { list-style: none; }

input, textarea { 
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  outline: none; 
}

blockquote, q { quotes: none; }
blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; }
strong, b { font-weight: bold; }
em, i { font-style: italic; }

table { border-collapse: collapse; border-spacing: 0; }
img { border: 0; max-width: 100%; }

h1 {
  display: block;
  font-family: 'Paytone One', Tahoma, sans-serif;
  font-size: 3.75em;
  line-height: 1.45em;
  margin-bottom: 25px;
  text-align: center;
  text-shadow: 1px 1px 0 #fff;
}

p {
  display: block;
  font-size: 1.7em;
  line-height: 1.3em;
  margin-bottom: 22px;
}

Beyond these resets the other portion of my CSS file targets the webpage layout itself. The photo image uses a rounded border-radius effect to create the circular profile picture. Everything else should be easy to understand without too many confusing properties.

/** page structure **/
#wrapper {
  display: block;
  max-width: 900px;
  margin: 0 auto;
  padding: 0 15px;
}

#photo {
  display: block;
  text-align: center;
  margin-bottom: 20px;
}
#photo img {
  width: 250px;
  height: 250px;
  border-radius: 125px;
  border: 5px solid #fff;
  box-shadow: 2px 0 7px #aaa;
}

.bigtext {
  display: block;
  overflow: hidden;
  color: #787878;
}

.expand, .contract {
  cursor: pointer;
  font-weight: bold;
  padding: 15px 0;
  text-align: center;
  color: #555;
}
.expand:hover, .contract:hover {
  color: #121212;
}

.hide {
  display: none;
}

The blockquote element uses an overflow of hidden to make sure the full text will not display without a fixed height value. Additionally the class of .hide is appended onto whichever expand/contract paragraph shouldn't be visible. So if the text is currently hidden then we don't need to see the contract link, just the expand link.

jQuery Animations

With all the page elements in place we can move into the jQuery code. It's pretty simple and using classes will allow this effect to work for an infinite number of expandable text blocks per page.

$(function(){
  var animspeed = 950; // animation speed in milliseconds
  
  var $blockquote = $('.bigtext');
  var height = $blockquote.height();
  var mini = $('.bigtext p').eq(0).height() + $('.bigtext p').eq(1).height() + $('.bigtext p').eq(2).height() + $('.bigtext p').eq(2).height();
  
  $blockquote.attr('data-fullheight',height+'px');
  $blockquote.attr('data-miniheight',mini+'px');
  $blockquote.css('height',mini+'px');

This first block creates a number of variables and creates a few dynamic HTML attributes too. First is the animation speed measured in milliseconds. The $blockquote object pulls from the container class to determine how tall it should be. You might create different variables for targeting individual text blocks if necessary.

The variable mini will determine how tall the paragraph should be when hiding text. This will display a bit of teaser content along with the expand link. jQuery has a method .eq() for selecting an individual element in a set of elements - in this case a number of paragraphs within the quote container. I've added together the height value of the first 3 paragraphs to get a solid number in pixels. This number will be the teaser height.

Then I'm creating new HTML5 data attributes for data-fullheight and data-miniheight. This will allow each unique quote element to self-contain the information it needs for expanding & contracting without any further calculations.

  $('.expand').on('click', function(e){
    $text = $(this).prev();
    
    $text.animate({
      'height': $text.attr('data-fullheight')
    }, animspeed);
    $(this).next('.contract').removeClass('hide');
    $(this).addClass('hide');
  });
  
  $('.contract').on('click', function(e){
    $text = $(this).prev().prev();
    
    $text.animate({
      'height': $text.attr('data-miniheight')
    }, animspeed);
    $(this).prev('.expand').removeClass('hide');
    $(this).addClass('hide');
  });
});

This last section of code will trigger when a user clicks either of the expand/contract paragraphs. The 2 jQuery selectors target a click event for both the .expand and .contract classes. When expanding we need to animate the container's CSS height to display all of the text. Also the .hide class is removed from the contract link and then applied onto the expand link, since the text is already expanded.

Likewise this process will animate in reverse when closing the text. I'm using .prev() to select the relative text container and adjust the height based on HTML5 data attributes created earlier. Since these attributes were calculated dynamically the values should always be correct.

Closing

One great thing about this effect is that it's easy to create and the animation is smooth on all devices. Any browser with JS capabilities can support this effect. Plus using classes allows you to append multiple paragraphs onto the same page using the same animation. Feel free to download a copy of my source code and customize this effect for your own web projects.