We’ve been experimenting lately with a CSS powered animation that replaces the refresh of the web page while the content is updated using ajax. Today’s nugget is based on the same idea, but with a different execution: this time a simple hover effect turns into a loading bar, that finally expands with new content!
Icons from our Nucleo library.
Creating the structure
The html structure is composed of a .cd-main
element, wrapping the page content, a .cd-side-navigation
, containing the side navigation, and a #cd-loading-bar
to create the loading bar animation.
<nav class="cd-side-navigation">
<ul>
<li>
<a href="index.html" class="selected" data-menu="index">
<svg><!-- svg content here --></svg>
Intro
</a>
</li>
<li>
<!-- ... -->
</li>
<!-- other list items here -->
</ul>
</nav> <!-- .cd-dashboard -->
<main class="cd-main">
<section class="cd-section index visible">
<header>
<div class="cd-title">
<h2>Animated Page Transition #2</h2>
<span>Some text here</span>
</div>
<a href="#index-content" class="cd-scroll">Scroll Down</a>
</header>
<div class="cd-content" id="index-content">
<!-- content here -->
</div> <!-- .cd-content -->
</section> <!-- .cd-section -->
</main> <!-- .cd-main -->
<div id="cd-loading-bar" data-scale="1" class="index"></div> <!-- lateral loading bar -->
Adding style
The .cd-side-navigation
is in fixed position, with a height: 100% and an overflow: hidden, while its child <ul> has an overflow-y: auto (so that you can scroll inside it if the navigation is higher than the viewport).
To create the line visible when you hover over each navigation item, we used the a::after
pseudo-elements. They are in absolute position, have a width: 4px , a top: 0 and a right: -4px.
One note: we assigned a width: calc(100%- 4px) to each list item elements to be able to place the a::after
elements inside the .cd-side-navigation
(since it has an overflow: hidden).
.cd-side-navigation {
position: fixed;
z-index: 3;
top: 0;
left: 0;
height: 100vh;
width: 94px;
overflow: hidden;
}
.cd-side-navigation ul {
height: 100%;
overflow-y: auto;
}
.cd-side-navigation::before {
/* background color of the side navigation */
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: calc(100% - 4px);
background-color: #131519;
}
.cd-side-navigation li {
width: calc(100% - 4px);
}
.cd-side-navigation a {
display: block;
position: relative;
}
.cd-side-navigation a::after {
/* 4px line to the right of the item - visible on hover */
content: '';
position: absolute;
top: 0;
right: -4px;
height: 100%;
width: 4px;
background-color: #83b0b9;
opacity: 0;
}
.no-touch .cd-side-navigation a:hover::after {
opacity: 1;
}
When a user selects a new item in the side navigation, a new .cd-section
element is created and inserted in the DOM (you can find more details in the ‘Events handling’ section).
By default, this new .cd-section
element is translated to the left, outside the viewport (translateX(-100%)). It is then moved back into the viewport (using the .visible
class ) to replace the old content.
.cd-section {
position: absolute;
z-index: 1;
top: 0;
left: 0;
height: 100%;
width: 100%;
transform: translateX(-100%);
transition: transform 0s 0.5s;
}
.cd-section.visible {
/* this is the visible/selected section */
position: relative;
z-index: 2;
transform: translateX(0);
transition: transform 0.5s 0s;
}
Events handling
The index.html file contains only the ‘Intro’ content. A different html file has been created for each navigation item (services.html, contact.html, ..) with exactly the same structure, but with different .cd-section
content.
When a user clicks one of the item in the side navigation, the triggerAnimation()
function is executed. This function triggers the loading bar animation (loadingBarAnimation()
function) and loads the new content (loadNewContent()
function).
For the loading bar animation, we have been using Velocity.js: at the beginning of the animation, the #cd-loading-bar
is placed right next to the selected navigation item (its height equal to the one of the list item). Its scaleY value is then animated to create the loading effect.
function loadingBarAnimation() {
//loadingBar is the #cd-loading-bar element
//scaleY is the loadingBar actual scale value
var scaleMax = loadingBar.data('scale'); //this is the scaleY value to cover the entire window height (100% loaded)
if( scaleY + 1 < scaleMax) {
newScaleValue = scaleY + 1;
}
// ...
loadingBar.velocity({
scaleY: newScaleValue
}, 100, loadingBarAnimation
);
}
When a new page is selected, a new .cd-section
element is created and inserted into the DOM. The load()
function is then used to load the new content (we used a data-menu
attribute assigned to the navigation list items to determine the file content to be loaded). Once the new html has been loaded, the loading bar animation is completed, the old content is replaced by the new one and the new page is added to the window.history (using the pushState()
method).
function loadNewContent(newSection) {
//create a new section element and insert it into the DOM (newSection is the data-menu of the selected navigation item)
var section = $('<section class="cd-section overflow-hidden '+newSection+'"></section>').appendTo(mainContent);
//load the new content from the proper html file
section.load(newSection+'.html .cd-section > *', function(event){
loadingBar.velocity({
scaleY: scaleMax //this is the scaleY value to cover the entire window height (100% loaded)
}, 400, function(){
//add the .visible class to the new section element -> it will cover the old one
section.addClass('visible');
var url = newSection+'.html';
if(url!=window.location){
//add the new page to the window.history
window.history.pushState({path: url},'',url);
}
// ...
}
);
});
}
To trigger the same animation effect when user clicks the browser back button, we listen for the popstate
event, and execute the triggerAnimation()
function when it is fired.