Advertisement
  1. Web Design
  2. UX/UI
  3. UX Design

Lovely, Smooth Page Transitions With the History Web API

Scroll to top

In this tutorial we’re going to build a website with beautifully smooth transitioning pages, without the usual aggressive page refresh. Navigate through the pages in the demo to see what I mean.

To achieve this effect we’ll use the History Web API. In a nutshell, this API is used to alter the browser history. It allows us to load a new URL, change the page title, then at the same time record it as a new visit in the browser without having to actually load the page.

This sounds confusing, but it opens up a number of possibilities–such as being able to serve smoother page transitions and give a sense of speediness which improves the user experience. You have probably already witnessed the Web History API in action on a number of websites and web applications, such as TrelloQuartz, and Privacy.

QuartzQuartzQuartz
The rather abstract (and rather nice) Quartz website

Before we go any further, let’s first look into one particular API that we are going deploy on the website.

The History Web API, in Brief

To access the Web History API, we first write window.history then follow this with one of the APIs; a method or a property. In this tutorial we’ll be focusing on the pushState() method, so:

1
window.history.pushState( state, title, url );

As you can see from the above snippet, the pushState() method takes three parameters. 

  1. The first parameter, state, should be an object containing arbitrary data. This data will then be accessible through window.history.state. In a real world application, we would pass data like a page ID, a URL, or serialized inputs derived from a form. 
  2. The last two parameters are title and
  3. url. These two change the URL and the document title in the browser, as well as record them as a new entry in the browser history. 

Let’s dissect the following example to better understand how the pushState() Method works.

1
(function( $ ){
2
3
	$( "a" ).on( "click", function( event ) {
4
5
		event.preventDefault();
6
7
		window.history.pushState( { ID: 9 }, "About - Acme", "about/" );
8
	} );
9
10
})( jQuery );

In the above code, a link attached with the click event then deploys the pushState() method. As we click on the link, we expect the code to change the document title and the URL:

Browsers WindowBrowsers WindowBrowsers Window
From top to bottom: Chrome, Firefox, Opera.

And it does; the screenshot shows the URL is changed to “about/” as defined in the pushState() method. And since the pushState() method creates a new record in the browser history, we are able to go back to the previous page through the browser’s Back button.

However, all the browsers in this example are currently ignoring the title parameter. You can see from the screenshot the document does not change to About - Acme as specified. Furthermore, calling the pushState() method won’t also trigger the popstate event; an event which is dispatched every time the history changes–something we need! There are a few discrepancies on how browsers handle this event, as stated in MDN:

“Browsers tend to handle the popstate event differently on page load. Chrome (prior to v34) and Safari always emit a popstate event on page load, but Firefox doesn’t.”

We will need a library as a fallback to make the History Web APIs work consistently across the browser without any hurdles.

Meet History.js

Since the pushState() method does not work to its full potential, in this tutorial we are going to leverage History.js. As the name implies, this JavaScript library is a polyfill, replicating the native History APIs that work across different browsers. It also exposes a set of methods similar to the native APIs, albeit with few differences.

As mentioned earlier, the browser native API is called through the history window object with the lowercase “h”, while the History.js API is accessed through History with the uppercase “H”. Given the previous example and assuming we have the history.js file loaded, we can revise the code, as follows (again, notice the uppercase “H”).

1
window.History.pushState( {}, title, url );

I hope this brief explanation is easy to understand. Otherwise, here are some further references if you want to learn more about the Web History API.

Building Our Static Website

In this section we won’t discuss each step needed to build a static website in detail. Our website is plain simple, as shown in the following screenshot:

The Website Homepage

You don’t have to create a website that looks exactly the same; you are free to add any content and create as many pages as you need. However, there are some particular points you need to consider regarding the HTML structure and the use of id and class attributes for some elements.

  1. Load jQuery and History.js within the document head. You may load it as a project dependency through Bower, or through a CDN like CDNJS or JSDelivr.
  2. Wrap the header, the content, and footer in a div with the ID wrap<div id="wrap"></div>
  3. There are a few navigation items on the website header and the footer. Each menu should be pointing to a page. Make sure the pages exist and have content.
  4. Each menu link is given page-link class which we will use for selecting these menus.
  5. Lastly, we give each link a title attribute which we’ll pass to pushState() to determine the document title.

Taking all this into account, our HTML markup will roughly look as follows:

1
<head>
2
	<script src="jquery.js"></script>
3
	<script src="history.js"></script>
4
</head>
5
<body>
6
	<div id="wrap">
7
		<header>
8
			<nav>
9
				<ul>
10
					<li><a class="page-link" href="./" title="Acme">Home</a></li>
11
					<li><a class="page-link" href="./about.html" title="About Us">About</a></li>
12
					<!-- more menu -->
13
				</ul>
14
			</nav>
15
		</header>
16
		<div>
17
			<!-- content is here -->
18
		</div>
19
		<footer>
20
			<nav>
21
				<ul>
22
					<li><a href="tos.html" class="page-link" title="Terms of Service">Terms</a></li>
23
					<!-- more menu -->
24
				</ul>
25
			</nav>
26
			<!-- this is the footer -->
27
		</footer>
28
	</div>
29
</body>

When you are done building your static website we can move on the main section of this tutorial.

Applying the History Web API

Before we begin writing any code, we need to create a new file to hold our JavaScript; we’ll name it script.js and load the file in the document before the body closing tag. 

Let’s add our first piece of code to change the document title and the URL upon clicking the menu navigation:

1
// 1.

2
var $wrap = $( "#wrap" );
3
4
// 2.

5
$wrap.on( "click", ".page-link", function( event ) {
6
	
7
	// 3.

8
	event.preventDefault();
9
	
10
	// 4.

11
	if ( window.location === this.href ) {
12
		return;
13
	}
14
	
15
	// 5.

16
	var pageTitle = ( this.title ) ? this.title : this.textContent;
17
		pageTitle = ( this.getAttribute( "rel" ) === "home" ) ? pageTitle : pageTitle + " — Acme";
18
	
19
	// 6.

20
	History.pushState( null, pageTitle, this.href );
21
} );

I’ve split the code apart into several numbered sections. These will make it easier for you to pinpoint the code with the following reference:

  1. On the first line, we select the element, <div id="wrap"></div>, that wraps all of our website content.
  2. We attach the click event. But, as you can see above, we attach it to the #wrap element instead of attaching the event directly on every menu navigation. This practice is known as event delegation. In other words, our #wrap element is responsible for listening to click events on behalf of .page-link.
  3. We’ve also added event.preventDefault() so that the users will not be directed to the page in question.
  4. If the clicked menu URL is the same as the current window we do not need to proceed to the next operation, simply because it is not necessary.
  5. The pageTitle variable contains the title format, derived from the link title attribute or the link text. Each page title follows {Page Title} — Acme convention, except for the home page. “Acme” is our fictitious company name.
  6. Lastly, we pass the pageTitle and the page URL to the History.js pushState() method.

At this point, when we click on the menu navigation, the title as well as the URL should change accordingly as shown below:

The browser window with updated URL and titleThe browser window with updated URL and titleThe browser window with updated URL and title
The page title and the URL are changed

Yet the page content remains the same! It is not updated to match the new title and the new URL. 

Content

We need to add the following lines of code to replace the actual page content.

1
// 1.

2
History.Adapter.bind( window, "statechange", function() {
3
	
4
	// 2.

5
	var state = History.getState();
6
	
7
	// 3.

8
	$.get( state.url, function( res ) {
9
10
		// 4.

11
		$.each( $( res ), function( index, elem ) {
12
			if ( $wrap.selector !== "#" + elem.id ) {
13
				return;
14
			}
15
			$wrap.html( $( elem ).html() );
16
		} );
17
18
	} );
19
} );

Again, the code here is split into several numbered sections.

  1. The first line of the code listens to the History change performed via the History.js pushState() method and runs the attached function.
  2. We retrieve the state changes, containing various data like a URL, title, and id.
  3. Through the jQuery .get() method we retrieve the content from the given URL.
  4. Lastly, we sort out the element with an id named wrap from the retrieved content, and eventually replace the current page content with it.

Once it’s added, the content should now be updated when we click on the menu navigation. As mentioned, we are also able to access visited pages back and forth through the browser Back and Forward buttons.

The browser window now with the updated contentThe browser window now with the updated contentThe browser window now with the updated content

Our website is presentable at this point. However, we would like to step further by adding a little animation to bring the page to life and, finally, our website feels more compelling.

Adding Animation and Transitions

Animation in this situation need only be simple, so we’ll write everything fro scratch, instead of loading animations through a library like Animate.cssMotion UI of ZURB, or Effeckt.css. We’ll name the animation slideInUp, as follows:

1
@keyframes slideInUp {
2
	from {
3
		transform: translate3d(0, 10px, 0);
4
		opacity: 0;
5
	}
6
	to {
7
		transform: translate3d(0, 0, 0);
8
		opacity: 1;
9
	}
10
}

As the name implies, the animation will slide the page content from bottom to top along with the element opacity. Apply the animation to the element that wraps the page main content, as follows.

1
.section {
2
	animation-duration: .38s;
3
	animation-fill-mode: both;
4
	animation-name: slideInUp;
5
}

The transition from one page to another one should now feel smoother once the animation is applied. Here, you may stop and call it a day! Our website is done and we are ready to deploy it for the world to see.

However, there is one more thing that you may need to consider adding, especially for those who want to monitor the number of visits and the visitors’ behavior on your website.

We need to add Google Analytics to track each page view.

Google Analytics

Since our pages will be loaded asynchronously (except for the initial page loaded) tracking the page view number should also be done asynchronously.

To begin with, make sure you have the standard Google Analytics added within the document head. The code usually looks something as follows:

1
<script>
2
		(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
3
			(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
4
			m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
5
		})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
6
7
		ga('create', 'UA-XXXXXX-XX', 'auto');
8
		ga('send', 'pageview');
9
10
	</script>

Then we need to adjust our JavaScript code to include the Google Analytics tracking code so that every page loaded asynchronously will also be measured as a page view.

We have several options. First, we can start counting when the user clicks a navigation link, or when changing the page title and URL, or when the content of the page has been fully loaded.

We’ll opt for the latter, which is arguably the most authentic, and in doing so we leverage the jQuery promise() method after we change the page content, as follows:

1
$wrap.html( $( elem ).html() )
2
		.promise()
3
			.done( function( res ) {
4
5
			// Make sure the new content is added, and the 'ga()' method is available.

6
			if ( typeof ga === "function" && res.length !== 0 ) {
7
				ga('set', {
8
					page: window.location.pathname,
9
					title: state.title
10
				});
11
				ga('send', 'pageview');
12
		}
13
	});

That’s all it is, we will now have the page view recorded in Google Analytics.

Wrapping Up

In this tutorial we have improved a simple static website with Web History API to make the page transition smoother, the load faster, and overall deliver a better experience to our users. At the end of this tutorial, we also implemented Google Analytics to record user page view asynchronously. Additionally, our website is perfectly crawl-able by search engine bots since it is, as mentioned, just a simple HTML website.

This was a meaty tutorial, explaining lots of things like CSS Animation, jQuery Ajax, and jQuery Promise. Here’s a handful of references for you to look into, to reinforce what you’ve learned.

Lastly, don’t forget to visit the demo site of this tutorial as well as the source code in the repository.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.