Multi-Layer Page Reveal Effects

A tutorial on how to create multi-layer page reveal effects with a couple of ideas for inspiration.

PageReveal_800x600

View demo Download source

Today we’d like to show you how to create a simple and modern page transition effect. Maybe you have spotted some of the cool effects seen on sites like Nation or Keep Portland Weird, where two or more layers of overlays are animated across the page to reveal some new content. Let’s see how we can do something like that. We’ll focus on the animation part only, not the loading of content. For the effects we’ll be using CSS Animations.

Attention: We’ll be using CSS Animations and viewport units which are only supported in modern browsers.

The Markup

For our simple demo, we’ll create a full width and height layout with some arrows that will trigger the overlay animations. The idea is to show a page and in the middle of an overlay animation, switch to another page. While we will be just working with static content, you could plug your dynamic solution in here that fetches some data on the fly.

Our markup looks as follows:

<body>
	<main class="container">
		<div class="pages">
			<div class="page page--current">
				<!-- intro page content -->
			</div><!-- /page -->
			<div class="page">
				<!-- some other page -->
			</div>
		</div><!-- /pages -->
		<nav class="pagenav">
			<!-- buttons that will trigger the overlay animations -->
		</nav>
	</main>
</body>

The structure for the overlay will be inserted with JavaScript. We’ll work with a division that is placed as a fixed element on top of everything else. Depending on the direction, we’ll give that element some classes that will help us apply styles properly. The overlay will contain a number of layers that we will be able to define. Each of the layers will have a background color that we’ll set and also a specific animation in our stylesheet.

One example for how the “revealer” might look:

<div class="revealer revealer--right">
	<div class="revealer__layer"></div>
	<div class="revealer__layer"></div>
	<div class="revealer__layer"></div>
</div>

Each layer will additionally have its background color set. In order to create unique effects, we want to animate each layer individually. For that we set the parent initially to an off-screen position and then animate the layers in. This initial setting of the main element is done in our script (see below). The animation behavior of the layers is then defined in our stylesheet depending on the effect class, along with other styles for the page to be revealed.

The CSS

First, let’s take a look at some general styles for our body, container and pages (vendor prefixes omitted):

html,
body {
	min-height: 100vh;
	overflow-x: hidden;
}

.js .container {
	position: relative;
	height: 100vh;
	overflow: hidden;
}

.js .pages {
	position: relative;
	overflow: hidden;
	width: 100vw;
	height: 100vh;
	z-index: 0;
}

.page {
	padding: 6.5em;
	background: #66c6ff;
	display: flex;
	flex-direction: column;
}

.js .page {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	visibility: hidden;
	z-index: 1;
}

.js .page--current {
	visibility: visible;
	position: relative;
}

We want our page to be full width and height and hide any overflow. By default, and with JS (we use Modernizr), the pages are hidden and the current class sets the respective one to be visible. We want to make sure that our layout is functional without JavaScript, so we add the “conditional” styles.

Let’s see how the style for the revealer looks like. So we want to place the revealer on top of everything, with a fixed position:


.revealer {
	width: 100vw;
	height: 100vh;
	position: fixed;
	z-index: 1000;
	pointer-events: none;
}

Depending on which direction we are currently using, we need to set some initial positioning styles. In our script, as we will see in a bit, we will set some other transforms in order to insure that the top side of our revealer is always the one facing towards the screen. This will simplify the effects because the inner layer animations will always be the same (moving upwards), so we don’t need to define new animations for each direction if the parent is rotated and positioned correctly:

.revealer--cornertopleft,
.revealer--cornertopright,
.revealer--cornerbottomleft,
.revealer--cornerbottomright {
	top: 50%;
	left: 50%;
}

.revealer--top,
.revealer--bottom {
	left: 0;
}

.revealer--right,
.revealer--left {
	top: 50%;
	left: 50%;
}

.revealer--top {
	bottom: 100%;
}

.revealer--bottom {
	top: 100%;
}

The layers will have a default background color which will be then targeted individually for each effect in a dynamic way:

.revealer__layer {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	background: #ddd;
}

Next, let’s check out an example effect. The three layer effect adds an animation to each layer with a special easing function. The anim–effect class is added to the body so that we know which effect we are currently using. The revealer–animate is given to the revealer in order to trigger the animations.

As we can see here, we are adding the same animation properties to all the layers, except for the animation-name. With three different animations, we define the delays of each within the keyframes. Like that we ensure that all animations end in the same moment but allow for different appearance timings:

.anim--effect-3 .page:nth-child(2) {
	background: #F3A3D3;
}

.anim--effect-3 .revealer--animate .revealer__layer {
	animation: anim-effect-3-1 1.5s cubic-bezier(0.550, 0.055, 0.675, 0.190) forwards;
}

.anim--effect-3 .revealer--animate .revealer__layer:nth-child(2) {
	animation-name: anim-effect-3-2;
}

.anim--effect-3 .revealer--animate .revealer__layer:nth-child(3) {
	animation-name: anim-effect-3-3;
}

@keyframes anim-effect-3-1 {
	0% {
		transform: translate3d(0, 0, 0);
	}
	25%, 75% {
		transform: translate3d(0, -100%, 0);
		animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
	}
	100% {
		transform: translate3d(0, -200%, 0);
	}
}

@keyframes anim-effect-3-2 {
	0%, 12.5% {
		transform: translate3d(0, 0, 0);
	}
	37.5%, 62.5% {
		transform: translate3d(0, -100%, 0);
		animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
	}
	87.5%, 100% {
		transform: translate3d(0, -200%, 0);
	}
}

@keyframes anim-effect-3-3 {
	0%, 25% {
		transform: translate3d(0, 0, 0);
		animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1.000);
	}
	75%, 100% {
		transform: translate3d(0, -200%, 0);
	}
}

What we do here is to make the first layer move first but then “pause” in the middle until 75% while on top of the visible screen. Then we move it to the opposite direction to reveal the new content. The same happens for the other two layers, just that here we start them at different keyframes, decreasing the “pause”. The last layer does not have a pause at all, it just goes through to the other side, starting at 25%.

The JavaScript

We did a little plugin that will create the revealer depending on some options. The default options are the following:


Revealer.prototype.options = {
	// total number of revealing layers (min is 1)
	nmbLayers : 1,
	// bg color for the revealing layers
	bgcolor : '#fff',
	// effect classname
	effect : 'anim--effect-1',
	// callback
	onStart : function(direction) { return false; },
	// callback
	onEnd : function(direction) { return false; }
};

The function for adding the revealer with its layers and respective effect class looks as follows:

Revealer.prototype._addLayers = function() {
	this.revealerWrapper = document.createElement('div');
	this.revealerWrapper.className = 'revealer';
	classie.add(bodyEl, this.options.effect);
	var  strHTML = '';
	for(var i = 0; i < this.options.nmbLayers; ++i) {
		var bgcolor = typeof this.options.bgcolor === 'string' ? this.options.bgcolor : (this.options.bgcolor instanceof Array && this.options.bgcolor[i] ? this.options.bgcolor[i] : '#fff');
		strHTML += '
'; } this.revealerWrapper.innerHTML = strHTML; bodyEl.appendChild(this.revealerWrapper); };

The most crucial function sets the initial positioning of our reveal element and adds the control classes for triggering the animation and for the chosen effect.

Depending on which direction we choose, we need to make sure that the reveal element gets the right transforms. Remember, we are simply rotating and positioning the revealer in a way so that the top side is always faced towards the screen so that the layers always move upwards. For the corner cases we need to make sure that the width and height of the revealer is set properly to the diagonal of the page. For the left and right case, we need to make sure that the height of the revealer is the width of the screen because we are rotating it 90 degrees.

Revealer.prototype.reveal = function(direction, callbacktime, callback) {
	// if animating return
	if( this.isAnimating ) {
		return false;
	}
	this.isAnimating = true;
	// current direction
	this.direction = direction;
	// onStart callback
	this.options.onStart(this.direction);

	// set the initial position for the layers´ parent
	var widthVal, heightVal, transform;
	if( direction === 'cornertopleft' || direction === 'cornertopright' || direction === 'cornerbottomleft' || direction === 'cornerbottomright' ) {
		var pageDiagonal = Math.sqrt(Math.pow(winsize.width, 2) + Math.pow(winsize.height, 2));
		widthVal = heightVal = pageDiagonal + 'px';
		
		if( direction === 'cornertopleft' ) {
			transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,135deg) translate3d(0,' + pageDiagonal + 'px,0)';
		}
		else if( direction === 'cornertopright' ) {
			transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,-135deg) translate3d(0,' + pageDiagonal + 'px,0)';
		}
		else if( direction === 'cornerbottomleft' ) {
			transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,45deg) translate3d(0,' + pageDiagonal + 'px,0)';
		}
		else if( direction === 'cornerbottomright' ) {
			transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,-45deg) translate3d(0,' + pageDiagonal + 'px,0)';
		}
	}
	else if( direction === 'left' || direction === 'right' ) {
		widthVal = '100vh'
		heightVal = '100vw';
		transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,' + (direction === 'left' ? 90 : -90) + 'deg) translate3d(0,100%,0)';
	}
	else if( direction === 'top' || direction === 'bottom' ) {
		widthVal = '100vw';
		heightVal = '100vh';
		transform = direction === 'top' ? 'rotate3d(0,0,1,180deg)' : 'none';
	}

	this.revealerWrapper.style.width = widthVal;
	this.revealerWrapper.style.height = heightVal;
	this.revealerWrapper.style.WebkitTransform = this.revealerWrapper.style.transform = transform;
	this.revealerWrapper.style.opacity = 1;

	// add direction and animate classes to parent
	classie.add(this.revealerWrapper, 'revealer--' + direction || 'revealer--right');
	classie.add(this.revealerWrapper, 'revealer--animate');

	// track the end of the animation for all layers
	var self = this, layerscomplete = 0;
	this.layers.forEach(function(layer) {
		onEndAnimation(layer, function() {
			++layerscomplete;
			if( layerscomplete === self.options.nmbLayers ) {
				classie.remove(self.revealerWrapper, 'revealer--' + direction || 'revealer--right');
				classie.remove(self.revealerWrapper, 'revealer--animate');
				
				self.revealerWrapper.style.opacity = 0;
				self.isAnimating = false;

				// callback
				self.options.onEnd(self.direction);
			}
		});
	});
		
	// reveal fn callback
	if( typeof callback === 'function') {
		if( this.callbacktimeout ) {
			clearTimeout(this.callbacktimeout);
		}
		this.callbacktimeout = setTimeout(callback, callbacktime);
	}
};

And that’s all folks, we hope you enjoyed this little effect and get inspired.

References and Credits

View demo Download source

Previous:
Next:

Tagged with:

ML is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

View all contributions by

Website: http://www.codrops.com

Related Articles

CSS Reference

Learn about all important CSS properties from the basics with our extensive and easy-to-read CSS Reference.

It doesn't matter if you are a beginner or intermediate, start leanirng CSS now.

Feedback 48

  1. 2

    Hello, thank you for a plugin, it is a good job.
    If you don’t mind I have a question, could you help me, please?
    I have tried to connect my navbar and logo(homepage) clicks to this plugin, but have a problem with the code, it will be great to solve this problem, Thank you very much.
    Here is the code by which I connected navbar and logo(homepage) to the plugin.

    var pages = [].slice.call(document.querySelectorAll(‘.pages > .page’)),
    currentPage = 0,

    revealerOpts = {
    // the layers are the elements that move from the sides
    nmbLayers : 3,
    // bg color of each layer
    bgcolor : [‘#52b7b9’, ‘#ffffff’, ‘#53b7eb’],
    // effect classname
    effect : ‘anim–effect-3’
    };
    revealer = new Revealer(revealerOpts);

    // clicking the page nav
    var navbar = document.getElementById(“navbar”).getElementsByTagName(“a”);
    for (var i = 0; i < navbar.length; i++) {
    navbar[i].addEventListener('click', function() {n = i; reveal('top'); });
    }

    // triggers the effect by calling instance.reveal(direction, callbackTime, callbackFn)
    function reveal(direction) {
    var callbackTime = 750;

    callbackFn = function() {

    // this is the part where is running the turning of pages
    classie.remove(pages[currentPage], 'page–current');

    currentPage = n;

    classie.add(pages[currentPage], 'page–current');

    };

    revealer.reveal(direction, callbackTime, callbackFn);
    }

Follow this discussion

Leave a Comment