CSS

Automatically generating a table of contents with a smooth scroll effect

Ever written a long article worthy of a table of contents and decided not to do it because it’s just too much hassle? This week we’ll be writing a jQuery plugin that will automatically search for your headings and create a table of contents based on them for your blog post. We’ll also be adding a smooth scroll effect, so that when a user clicks on an entry they are brought to the right section. I know a table of contents is a bit of an unusual topic for a tutorial, but if you’re writing a help file for your product, or an FAQ it’s just as applicable. This tutorial also serves as an introduction to jQuery basics.

Getting started

The first thing we need to do is create our jQuery plugin. The plugin will have a configurable title and duration option for the scroll animation, and in theory there could be multiple elements we want to apply the logic to, so we’ll be using the each pattern. Our basic plugin will look something like this

(function ( $ ) {
    $.fn.TableOfContents = function( options ) {
        return this.each(function() {
        });
     };
}( jQuery ));

We can provide the default values for our configuration options by using the .extend method and passing in the user provided options. This means we can access all the configuration options using the settings variable.

(function ( $ ) {
    $.fn.TableOfContents = function( options ) {
        var settings = $.extend({
            duration: "500",
            title: "Contents"
        }, options );
 
        return this.each(function() {
        });
     };
}( jQuery ));

Building our table of contents

To actually build our table of contents, we put our core logic in the each function. We’ll first need to identify the target element, insert our own HTML and then find all of the headings. Using the .prepend function, we can insert our main div element at the top of the target. At this point we’re also going to specify the title of the table. We’ll also need to get a reference to our list element for future reference.

(function ( $ ) {
    $.fn.TableOfContents = function( options ) {
        var settings = $.extend({
            duration: "500",
            title: "Contents"
        }, options );
 
        return this.each(function() {        
            var article = $(this);
            article.prepend('<div class="table-of-contents"><h1>' + settings.title + '</h1><ul></ul></div>');
            var list = article.find('div.table-of-contents:first > ul:first');
        });
     };
}( jQuery ));

Actually finding all of the heading elements is quite easy, we simple use the .find function and specify the selector for the header elements in CSV format. Then using the .each function we iterate through each heading and grab the type of header it is, the text and the ID. If the ID is not set, we’ll need to specify one so that we have something to point our anchor too.

(function ( $ ) {
    $.fn.TableOfContents = function( options ) {
        var settings = $.extend({
            duration: "500",
            title: "Contents"
        }, options );
 
        return this.each(function() {
            var article = $(this);
            article.prepend('<div class="table-of-contents"><h1>' + settings.title + '</h1><ul></ul></div>');
            var list = article.find('div.table-of-contents:first > ul:first');
            
            article.find('h1, h2, h3, h4').each(function(){
                var heading = $(this);
                var tag = heading[0].tagName.toLowerCase();
                var title = heading.text();
                var id = heading.attr('id');
                
                if(typeof id === "undefined") {
                    id = Math.random().toString(36).substring(7);
                    heading.attr('id', id);
                }
                list.append('<li class="' + tag + '"><a href="#' + id + '" title="' + title + '">' + title + '</a></li>');
            });
        });
    };
}( jQuery ));

We’re going to use the tag type to apply a class to our list item. This means we can easily indent and style the item whatever way we want.

table-of-no-css

Adding the scroll animation

To add the scroll animation, we’re going to intercept the click event on the list element instead of on each item. We do this as it means attaching only one event instead of many, simply put it’s less resource hungry and more efficient. To do this properly, we need to first get the target of the click event, check to see if it’s an a tag. If it is, we need to prevent the default action using .preventDefault. We can then apply our animation, and finally we return false. We return false, to prevent any further events from being raised after this event has completed.

(function ( $ ) {
    $.fn.TableOfContents = function( options ) {
        var settings = $.extend({
            duration: "500",
            title: "Contents"
        }, options );
 
        return this.each(function() {
            var article = $(this);
            article.prepend('<div class="table-of-contents"><h1>' + settings.title + '</h1><ul></ul></div>');
            var list = article.find('div.table-of-contents:first > ul:first');
            
            article.find('h1, h2, h3, h4').each(function(){
                var heading = $(this);
                var tag = heading[0].tagName.toLowerCase();
                var title = heading.text();
                var id = heading.attr('id');
                
                if(typeof id === "undefined") {
                    id = Math.random().toString(36).substring(7);
                    heading.attr('id', id);
                }
                list.append('<li class="' + tag + '"><a href="#' + id + '" title="' + title + '">' + title + '</a></li>');
            });
            
            list.on('click', function(event){
                var target = $(event.target);
                
                if(target.is('a')){
                    event.preventDefault();
                    jQuery('html, body').animate({
                        scrollTop: $(target.attr('href')).offset().top
                    }, settings.duration);
                    return false;
                }
            });
        });
 
    };
 
}( jQuery ));

The .animate allows us to specify the property we want our final position to be, in this case scrollTop. Scroll top, is the current position of the top of the scrollbar. We want to scroll to the header that matches our anchor point (href). We get the position using the .offset function and setting scrollTop to the value of the top property

Styling the table of contents

Styling the table of contents is very easy, I’ve deliberately kept the output HTML as simple as possible. Instead of outputting lists with in lists ( e.g ul li ul li ) like a hierarchical menu, we just have to specify the margin-left attribute to position our sub-headings.

First we’ll remove any margins and padding that might be applied to the list already. We’ll also make sure that the list-style is set to none.

div.table-of-contents > ul {
    margin: 0px;
    padding: 0px;
    list-style: none;
}

div.table-of-contents > ul li{
    margin: 0px;
    padding: 0px;
}

Now we can create the classes we need for our headers. I’ve chosen to indent them only slightly, but you could align them differently or use different fonts.

div.table-of-contents > ul li.h1{
    margin-left: 10px;
}

div.table-of-contents > ul li.h2{
    margin-left: 20px;
}

div.table-of-contents > ul li.h3{
    margin-left: 30px;
}

div.table-of-contents > ul li.h4{
    margin-left: 40px;
}

table-of-with-css

And there you go, a simple way of navigating the content on your page. The source code below contains both the HTML example and a separate jQuery plugin file.

If you’re using this on a WordPress site you can add jQuery to your template like this using add_action and wp_enqueue_script

function theme_enqueue_script(){ 
    wp_enqueue_script('jquery');  
}
add_action('wp_enqueue_scripts', 'theme_enqueue_script');

A battle hardened software developer with a mixed and colorful background, who can't come up with a decent author bio More articles by Jonathan Schnittger
Home CSS Deals DesignBombs HTML HTML5 JavaScript jQuery Miscellaneous Mobile MySQL News PHP Resources Security Snippet Tools Tutorial Web Development Web Services WordPress