jQuery Tabs Script With HTML 5 History (PushState)
You can find quite a few jQuery tabs scripts around the web, including jQuery’s own tabs. However I thought I’d do a little tutorial showing how to build your own, and how to include support for the HTML 5 history system (AKA: PushState).
Important Note
This tutorial will probably get a little complicated. It involves using the HTML 5 history API and some code that jQuery beginners may find confusing.
Also another quick note. This script will only allow 1 set of tabs per page.
jQuery Tabs With HTML 5 History: Demo
So as I know you love a demo, here is one just for you.
Now that you’ve seen the jQuery Tabs script with HTML 5 History demo, lets get on with how it was built. First let’s start with the HTML.
jQuery Tabs With HTML 5 History: HTML
The HTML for this tutorial is pretty simple. You just need the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<div class="rt-tabs"> <ul class="tabs cf"> <li> <a href="#tab-1">Tab 1</a> </li> <li> <a href="#tab-2">Tab 2</a> </li> <li> <a href="#tab-3">Tab 3</a> </li> </ul> <div data-id="tab-1" class="box"> Data data everywhere, but nothing interesting at all. </div> <div data-id="tab-2" class="box"> Engage, Number One. </div> <div data-id="tab-3" class="box"> Welcome watchers of illusion to the castle of Confusion. </div> </div> |
As I said, the HTML is pretty simple. It consists of an outer container, a unordered list (for the tabs), and a matching set of division tags to hold the data.
It is important to note the data-id
, this attribute will be used by our jQuery to identify which box of data to show. Same applies for the hyperlinks in our unordered list. These must match our data-id
.
Now that we’ve taken a look at the HTML, let’s take a look at the jQuery.
jQuery Tabs With HTML 5 History: jQuery
This is where things get a little complicated. I’ll try my best to explain it in the simplest terms possible, but if you do have any questions or are confused about anything please drop a comment & I’ll try to help out however I can.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
jQuery(function($) { var $t = $('.rt-tabs'), push = false; if($t.length > 0) { $('.box', $t).hide(); $('ul li a', $t).click(function() { loadTab($(this)); return false; }); $(window).on("popstate", function() { push = true; hashCheck($t); }); function hashCheck($t) { var h = (window.location.href.indexOf("#") > -1) ? window.location.href.split("#").pop() : false; if(h) { $('ul li a[href="#'+h+'"]', $t).click(); } else { $('ul li a:first', $t).click(); } } function loadTab(_self) { $('ul li', $t).removeClass('active'); $('.box', $t).hide(); var hash = _self.prop('hash'); if(!push) history.pushState({}, '', hash); var hash = hash.substr(1); $('.box[data-id="'+hash+'"]').show(); _self.parent('li').addClass('active'); push = false; } } }); |
It’s a big block of code, I know, but let’s go through it a bit at a time.
1 |
jQuery(function($) { |
First we have our DOM ready. I normally use this style all the time as force of habit from working with WordPress.
1 2 3 |
var $t = $('.rt-tabs'), push = false; if($t.length > 0) { |
Next we create a couple of variables. The first saves repeating the selector for the tabs & saves memory. The second is to track if we need to use pushState
, I’ll explain more soon. The last line checks to see if we have at least one set of tabs before running the rest of the code.
1 2 3 4 |
$('ul li a', $t).click(function() { loadTab($(this)); return false; }); |
This sets up a click event on our tabs. The click event triggers a function called loadTab()
and passes the current clicked elements selector through (as a jQuery object) for the function to use.
1 2 3 4 |
$(window).on("popstate", function() { push = true; hashCheck($t); }); |
Next we setup a popstate
event. popstate
is triggered when the current browser history is changed such as using pushState
(but only in an event such as a click) or when the back button on the browser is pressed. This event just sets our push
variable to true
and triggers the hashCheck()
function, passing the jQuery object containing the tabs to it.
1 2 3 4 5 6 7 8 |
function hashCheck($t) { var h = (window.location.href.indexOf("#") > -1) ? window.location.href.split("#").pop() : false; if(h) { $('ul li a[href="#'+h+'"]', $t).click(); } else { $('ul li a:first', $t).click(); } } |
Next the function that we called earlier. hashCheck()
tries to split the hash out of the URL. If it finds a hash it activates the correct tab, if it doesn’t it activates the first tab. Now you can grab the hash from the URL using window.location.hash
and this gives you the hash with the pound (#) included, but if there is no hash it gives you the full URL. This was no good for my purposes so I decided to use that code instead.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function loadTab(_self) { $('ul li', $t).removeClass('active'); $('.box', $t).hide(); var hash = _self.prop('hash'); if(!push) history.pushState({}, '', hash); var hash = hash.substr(1); $('.box[data-id="'+hash+'"]').show(); _self.parent('li').addClass('active'); push = false; } |
Finally the loadTab
function. This function actually loads the tabs when they are clicked. How does it do it? Well like this.
1 2 |
$('ul li', $t).removeClass('active'); $('.box', $t).hide(); |
First we remove any active classes that may already be on the tabs. We also hide all of the boxes triggered by the tabs.
1 2 3 4 |
var hash = _self.prop('hash'); if(!push) history.pushState({}, '', hash); |
Then we grab the hash from the clicked tab. This hash is the one from the hyperlinks I mentioned earlier (remember?). Also if push
, our history tracker, is false we need to add this click to the browser history. So we call history.pushState()
to add the URL + its hash to the history.
1 2 3 4 5 |
var hash = hash.substr(1); $('.box[data-id="'+hash+'"]').show(); _self.parent('li').addClass('active'); push = false; |
Now we strip the pound (#) from the hash & use it to show the correct box using the data-id
attribute to find it. Then we travel up the DOM from the clicked hyperlink to add the active class to the tab. Finally we make sure our push
variable is now set to false.
Why Do You Need the push
variable?
Well there is a small bug caused by not wanting to repeat any code & that variable fixes it. Because the same function is used to load a tab whether you click it or it is triggered by the popstate
event, it will add your going back using the browser history to the history. That means you will never actually go back. It’s a little mind numbing to understand but here is what happens.
- You click ‘tab 1’.
- You click ‘tab 2’.
- You click ‘tab 3’.
- You click the back button on the browser. You end up back on ‘tab 2’.
- As you go back to ‘tab 2’ the
hashCheck
function emulates a click on the correct tab (so that the correct tab is loaded) causingpushState
to be triggered & a second ‘tab 2’ entry to be recorded. - Now no matter how many times you click the browser’s back button, you are stuck on ‘tab 2’.
Because the push
variable allows us to know if the tab was triggered by popstate
or a true click we can avoid triggering pushState
in popstate
cases and avoid this problem completely.
Hopefully that makes sense.
jQuery Tab With HTML 5 History: CSS
I’m not going to include the CSS here, but if you take a look at the demo (look up a little) you can use the CSS from that page just by viewing the page source in your browser. All the CSS on that page is for the tabs & it is just some basic styling I created so feel free to use it as you wish.
jQuery Tab With HTML 5 History: Summary
I basically wanted to show how to make a tab system, but with a twist & I felt the HTML 5 History API added a nice bit of ‘Je ne sais quoi’ to it. Anyway hopefully this has helped you to learn how to create a tab system, and some basics of how to use the HTML 5 History API. I’m just starting out using it myself so if you have any tips you’d like to add or you’ve spotted a mistake please let me know in the comments & I’ll correct it.
I’m aware that this will not work in browsers that do not support the HTML 5’s History API (IE 9 <), but I thought I'd leave that part for you as a little project. I'll give you a hint in that you can either use Modernizer to detect support & disable pushState
for browser that don’t support it, or maybe you can emulate the system using some clever JavaScript polyfills/scripts.
4 Comments
Vlad
Hello, these tabs are perfect! However, just recently I noticed on Chrome the first tab does not load automatically.. The content is empty, until you click on the button.. Before it would load the first tab automatically but now it doesn’t.. Do you know what could be the problem? Thanks!
Paul Robinson
Hi Vlad,
It looks as though Chrome may have changed it’s popstate behaviour and no longer triggers it on page load. If this is the case you would simply just need to call the
hashCheck()
function at the end of the DOM ready. That should trigger the first tab to show on page load.Hitesh
Hi, There is one issue i.e., when you click on a particular tab mutiple times it will stuck the user on the same state on popstate phase. Need to handle this situation as well.
Paul Robinson
Hi Hitesh,
Do you mean that if you click on the same tab multiple times it records those clicks in the history and using the back button appears to do nothing (it does though!)?
If so you could simply add a check to the
hashCheck
function to check if the tab clicked was the same as the currently active tab and return out. Also please do keep in mind this is quite old now and while it still works there may be ways to improve it do to advances in browser technology.