Putting CSS Clip to Work: Expanding Overlay Effect

A tutorial about how to create a simple expanding overlay effect using the CSS clip property and CSS transitions.

Putting CSS Clip to Work: Expanding Overlay Effect

View demo Download source

Our previous article, Understanding the CSS Clip Property by Hugo Giraudel offers a great overview of the CSS clip property and the rect() function. Today we want to explore the practical side of it a little bit more. We are going to create a neat and simple effect for revealing some extra content and expanding a fullscreen overlay.

We want to show how to leverage the CSS clip property to make a smooth transition when clicking on a box element. The idea is to show some kind of overlay as if it’s actually underneath the respective element. Clicking an element will create a cut-out effect, revealing another layer that will expand.

This is how we’ll do it: we will first create a list of items that will look like metro-style boxes:

expandingoverlay1

Each one of the boxes will contain an element (overlay) that will be of position fixed. This element will actually spread over all the page but we won’t see it because the opacity will be set to 0. When we click on a box, we’ll use clip: rect() to crop the respective part of the inner fixed element. Then we will animate the clip to reveal all the width and height of the overlay which is our entire viewport:

ExpandingOverlay2

Clicking on the close button will reverse the effect and the overlay will minimize to the list item’s size and disappear.

So, let’s get started with the HTML.

The Markup

We’ll use an unordered list for the boxes. Each list item will have an icon class and an optional “span” class that will control the width of the box. Inside we’ll add some text and the overlay division. The overlay will contain a structure that will have a column layout. Since we chose a dummy weather app as our theme, we will be showing a weather forecast for the next seven days. Each of the “day” columns will have some spans which we’ll use for the weekdays, the weather icon and the temperature:

<ul id="rb-grid" class="rb-grid clearfix">

	<li class="icon-clima-1 rb-span-2">

		<h3>Lisbon</h3>
		<span class="rb-temp">21°C</span>

		<div class="rb-overlay">
			<span class="rb-close">close</span>
			<div class="rb-week">
				<div><span class="rb-city">Lisbon</span><span class="icon-clima-1"></span><span>21°C</span></div>
				<div><span>Mon</span><span class="icon-clima-1"></span><span>19°C</span></div>
				<div><span>Tue</span><span class="icon-clima-2"></span><span>19°C</span></div>
				<div><span>Wed</span><span class="icon-clima-2"></span><span>18°C</span></div>
				<div><span>Thu</span><span class="icon-clima-2"></span><span>17°C</span></div>
				<div><span>Fri</span><span class="icon-clima-1"></span><span>19°C</span></div>
				<div><span>Sat</span><span class="icon-clima-1"></span><span>22°C</span></div>
				<div><span>Sun</span><span class="icon-clima-1"></span><span>18°C</span></div>
			</div>
		</div>
		
	</li>

	<li class="icon-clima-2">
		<h3>Paris</h3><span class="rb-temp">11°C</span>
		<div class="rb-overlay">
			<!-- ... -->
		</div>
	</li>

	<li><!-- ... --></li>

	<!-- ... -->

</ul>

Let’s move on to the style.

The CSS

Note that the CSS will not contain any vendor prefixes, but you will find them in the files.

The unordered list will be centered in its parent and we’ll remove the list style:

.rb-grid {
	list-style: none;
	text-align: center;
	margin: 0 auto;
}

The list items will have a fluid width and we’ll give them a height of 15em. The will float left:

.rb-grid li {
	width: 24%;
	height: 15em;
	margin: 0.5%;
	background: #8CC7DF;
	color: #fff;
	display: block;
	float: left;
	padding: 1.6em;
	cursor: pointer;
	position: relative;
}

There are three different widths for our grid items, the “default” one with 24% and then the following other two:

.rb-grid li.rb-span-2 {
	width: 49%;
}

.rb-grid li.rb-span-4 {
	width: 99%;
}

Let’s style the city name headline:

.rb-grid li h3 {
	font-size: 2.6em;
	font-weight: 100;
}

We are including a CSS file for the icon font that we are using. It’s the Climacons Font by Adam Whitcroft. You can check out the climacons.css to see which icons we are including. Basically we are using an icon class to add an icon with a pseudo element. In our grid we want them to be positioned absolutely in the lower right corner, appearing a bit cut off:

.rb-grid li[class^="icon-"]:before,
.rb-grid li[class*=" icon-"]:before {
	font-size: 10em;
	position: absolute;
	display: block;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	line-height: 3;
	opacity: 0.4;
	text-align: right;
	pointer-events: none;
}

The temperature will be semi-transparent and we’ll add a transition for its opacity:

.rb-temp {
	display: block;
	font-size: 2em;
	opacity: 0.5;
	transition: all 0.3s ease-in-out;
}

When hovering over a list item, we’ll simply increase it:

.rb-grid li:hover .rb-temp {
	opacity: 1;
}

Now, let’s take a look at the important overlay division. The final view that we’d like to have is a fullscreen overlay, so we’ll set it’s width and height to 100%. We don’t want it to be relative to anything and we want it to be on top of everything, so let’s give it fixed positioning. Since this would make it appear on top of everything and we’d have overlapping, huge overlays all over, we need to initially set the z-index to -1. This will put them all behind the content of the page. Setting the opacity to 0 will make them invisible:

.rb-overlay {
	opacity: 0;
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	transition: all 0.4s ease;
	z-index: -1;
	pointer-events: none;
	cursor: default;
}

That’s the initial state of the overlays. Once we click on a list item, we will set the correct rect() values for the clip property and expand the overlay by animating the rect() values.

But let’s first take a look at the rest of the styling.

Each overlay will have a little close “button” which will be positioned in the top right corner:

.rb-close {
	position: absolute;
	top: 0.4em;
	right: 0.4em;
	width: 2em;
	height: 2em;
	text-indent: -9000px;
	cursor: pointer;
	z-index: 1000;
}

.rb-close:before {
	content: 'x';
	font-weight: 100;
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	font-size: 3em;
	line-height: 0.6;
	text-align: center;
	text-indent: 0px;
}

The wrapper for the columns will have the class rb-week (although we also include the current weather column in it). We need to set it to 100% width and height so that we can define the heights and widths for its children correctly:

.rb-week {
	width: 100%;
	height: 100%;
}

The “columns” will have a width of 10% (except the first one, which will have 30%) and they will be floating left:

.rb-week > div {
	width: 10%;
	height: 100%;
	float: left;
	position: relative;
	padding: 3% 0;
}

.rb-week > div:first-child {
	width: 30%;
}

We have eight columns in total, so 10% times 7 results in 70%, so we have 30% left for the first column.

Each of the spans will have a height of 30% and some padding:

.rb-week span {
	padding: 5% 0;
	font-size: 2em;
	font-weight: 100;
	display: block;
	margin: auto 0;
	height: 30%;
	width: 100%;
	line-height: 0.8;
}

The span for the city name will have a special style, with a thinner font weight:

.rb-week span.rb-city {
	font-weight: 700;
	padding: 1% 10%;
	font-size: 1em;
	line-height: 1.2;
}

The icons will have an increased font size and we’ll need to reset the font weight because we’ve changed it in the other rule:

.rb-week [class^="icon-"]:before {
	font-size: 2.5em;
	font-weight: normal;
}

The icon in the “current weather column” will be almost transparent:

.rb-week > div:first-child [class^="icon-"] {
	opacity: 0.1;
}

Now, let’s just go completely nuts and define different background colors for each and every list item box and each column in the overlays.

We have 11 list items:

/* Colors */

/* Grid */
.rb-grid li:nth-child(1) { background: #3399CC; }
.rb-grid li:nth-child(2) { background: #33CCCC; }
.rb-grid li:nth-child(3) { background: #996699; }
.rb-grid li:nth-child(4) { background: #C24747; }
.rb-grid li:nth-child(5) { background: #e2674a; }
.rb-grid li:nth-child(6) { background: #FFCC66; }
.rb-grid li:nth-child(7) { background: #99CC99; }
.rb-grid li:nth-child(8) { background: #669999; }
.rb-grid li:nth-child(9) { background: #CC6699; }
.rb-grid li:nth-child(10) { background: #339966; }
.rb-grid li:nth-child(11) { background: #666699; }

And for each overlay we have eight columns:


/* Overlay Columns */
.rb-grid li:nth-child(1) .rb-week > div:nth-child(1) { background: #3399CC; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(2) { background: #2D87B4; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(3) { background: #297AA3; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(4) { background: #256E93; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(5) { background: #216283; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(6) { background: #1D5672; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(7) { background: #184962; }
.rb-grid li:nth-child(1) .rb-week > div:nth-child(8) { background: #143D52; }

.rb-grid li:nth-child(2) .rb-week > div:nth-child(1) { background: #33CCCC; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(2) { background: #2DB4B4; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(3) { background: #29A3A3; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(4) { background: #259393; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(5) { background: #218383; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(6) { background: #1D7272; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(7) { background: #186262; }
.rb-grid li:nth-child(2) .rb-week > div:nth-child(8) { background: #145252; }

/* ... */

…and so on for each of the 11 boxes.

Last but not least, let’s take care of smaller screens with a media query.
When the space is limited, we don’t want to show boxes in the grid anymore:

@media screen and (max-width: 63.125em) {
	
	.rb-grid li,
	.rb-grid li.rb-span-2,
	.rb-grid li.rb-span-4 {
		width: 100%;
		height: 10em;
		text-align: left;
	}

	.rb-grid li[class^="icon-"]:before,
	.rb-grid li[class*=" icon-"]:before {
		font-size: 6em;
		left: auto;
		right: 0;
		line-height: 2.5;
	}

	.rb-grid li > div {
		text-align: center;
	}
}

The overlay columns and the text inside will be taken care of with the FitText plugin so we won’t have to change the layout drastically.

Now, let’s take a look at some important parts of the JavaScript.

The JavaScript

Let’s start by caching some elements and initialize some variables:

var $items = $( '#rb-grid > li' ),
	transEndEventNames = {
		'WebkitTransition' : 'webkitTransitionEnd',
		'MozTransition' : 'transitionend',
		'OTransition' : 'oTransitionEnd',
		'msTransition' : 'MSTransitionEnd',
		'transition' : 'transitionend'
	},

	// transition end event name
	transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],

	// window and body elements
	$window = $( window ),
	$body = $( 'BODY' ),

	// transitions support
	supportTransitions = Modernizr.csstransitions,

	// current item's index
	current = -1,

	// window width and height
	winsize = getWindowSize();

First, we will apply the FitText jQuery plugin to the column text elements in the overlay in order to scale down the text for smaller screens.
We’ll then bind the click events to the items and the close buttons (per item).

Also, we need to get the current values for the window’s width and height, so we’ll bind the resize event to the window element.

function init( options ) {		
	// apply fittext plugin
	$items.find( 'div.rb-week > div span' ).fitText( 0.3 ).end().find( 'span.rb-city' ).fitText( 0.5 );
	initEvents();
}

When we click on one item, two transitions are going to be applied to the respective overlay element. The first one will apply a clip that will crop it in the exact same place of the current list item. We’ll also show the overlay by increasing its opacity. The second one will take care of animating the clip so that the overlay “expands” to fit the window’s width and height. For the first transition the values needed are respective to the item’s position and dimensions. We get those by calling the “getItemLayoutProp” function. For the second one we just need the window’s width and height to define the right clip value.

Two things also need to be considered here in order for this to work properly. First, we are “showing and hiding” the page scroll between states because we don’t want to be able to scroll once the final state (expanded overlay) is reached. Second, we are setting the overlay’s z-index to a high value so it stays on “top” and the pointer-events to auto so that the overlay content is clickable.
If transitions are not supported we are basically skipping the first state and the overlay will be expanded immediately when the item is clicked.

function initEvents() {
	
	$items.each( function() {

		var $item = $( this ),
			$close = $item.find( 'span.rb-close' ),
			$overlay = $item.children( 'div.rb-overlay' );

		$item.on( 'click', function() {

			if( $item.data( 'isExpanded' ) ) {
				return false;
			}
			$item.data( 'isExpanded', true );
			// save current index of the item
			current = $item.index();

			var layoutProp = getItemLayoutProp( $item ),
				clipPropFirst = 'rect(' + layoutProp.top + 'px ' + ( layoutProp.left + layoutProp.width ) + 'px ' + ( layoutProp.top + layoutProp.height ) + 'px ' + layoutProp.left + 'px)',
				clipPropLast = 'rect(0px ' + winsize.width + 'px ' + winsize.height + 'px 0px)';

			$overlay.css( {
				clip : supportTransitions ? clipPropFirst : clipPropLast,
				opacity : 1,
				zIndex: 9999,
				pointerEvents : 'auto'
			} );

			if( supportTransitions ) {
				$overlay.on( transEndEventName, function() {

					$overlay.off( transEndEventName );

					setTimeout( function() {
						$overlay.css( 'clip', clipPropLast ).on( transEndEventName, function() {
							$overlay.off( transEndEventName );
							$body.css( 'overflow-y', 'hidden' );
						} );
					}, 25 );

				} );
			}
			else {
				$body.css( 'overflow-y', 'hidden' );
			}

		} );

		...

	} );

	...

}

function getItemLayoutProp( $item ) {
		
	var scrollT = $window.scrollTop(),
		scrollL = $window.scrollLeft(),
		itemOffset = $item.offset();

	return {
		left : itemOffset.left - scrollL,
		top : itemOffset.top - scrollT,
		width : $item.outerWidth(),
		height : $item.outerHeight()
	};

}

As for the click event for the close element, we are basically reverting what was done before:

function initEvents() {
	
	$items.each( function() {

		...

		$close.on( 'click', function() {

			$body.css( 'overflow-y', 'auto' );

			var layoutProp = getItemLayoutProp( $item ),
				clipPropFirst = 'rect(' + layoutProp.top + 'px ' + ( layoutProp.left + layoutProp.width ) + 'px ' + ( layoutProp.top + layoutProp.height ) + 'px ' + layoutProp.left + 'px)',
				clipPropLast = 'auto';

			// reset current
			current = -1;

			$overlay.css( {
				clip : supportTransitions ? clipPropFirst : clipPropLast,
				opacity : supportTransitions ? 1 : 0,
				pointerEvents : 'none'
			} );

			if( supportTransitions ) {
				$overlay.on( transEndEventName, function() {

					$overlay.off( transEndEventName );
					setTimeout( function() {
						$overlay.css( 'opacity', 0 ).on( transEndEventName, function() {
							$overlay.off( transEndEventName ).css( { clip : clipPropLast, zIndex: -1 } );
							$item.data( 'isExpanded', false );
						} );
					}, 25 );

				} );
			}
			else {
				$overlay.css( 'z-index', -1 );
				$item.data( 'isExpanded', false );
			}

			return false;

		} );

	} );

	...

}

And that’s it! I hope you enjoyed this tutorial and find it useful!

Also check out demo 2 which has an additional fancy effect for revealing the overlay. The rotation effect is done using a simple transition; it’s a great example of how simple it is add you own unique effect.

View demo Download source

Previous:
Next:

Tagged with:

Mary Lou (Manoela Ilic) 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://tympanus.net/

Related Articles

Feedback 74

Comments are closed.
  1. 3

    Amazing work! would make an awesome wordpress theme if you could have blog posts instead of the weather!

  2. 5

    Awesome, and thanks a lot for the tutorial.
    Can you share with me where did you get the icons?

  3. 8

    I am just a fan of yours! and my new website will be full of thanks for you!
    love from Barcelona

  4. 9

    To reply to Harshit Laddha, you need to change width and height in this code.

    .rb-overlay {
    opacity: 0;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    -webkit-transition: all 0.4s ease;
    -moz-transition: all 0.4s ease;
    transition: all 0.4s ease;
    z-index: -1;
    pointer-events: none;
    cursor: default;
    }

  5. 11

    This is such an amazing tutorial!! Please keep going with these tutorials! Thank you SO much for this!!

  6. 12

    After seeing so many awesome works from Mary Lou’s, I have kind of become expert on guessing who did what. So, this has been the fifth time that I guessed it right. I watched the demo and immediately thought of Mary Lou. Then I scrolled down the end of the article and see, I win. Great stuff.

  7. 13

    Great work Mary Lou!

    Just one question. I want to overlay external content. Is it possible?

    Thanks!

  8. 15

    This is really cool! I’m already thinking of how useful this will be for certain applications. You just upped my development skills! Thank you so much for sharing this code.

  9. 16

    Its really good effect!!! Thanks for sharing it ….can you provide some information on how can we call the iframed pages in the overlay ???

  10. 17

    Very good tutorial. I’d been dreaming about these effects since 2009 and managed to do it with flash. At last I’ve made a good webpage with the help of your tutorial.

  11. 18

    Hello,

    This is an awesome tutorial and i am excited to use it in my future projects. I was kind playing around with this tutorial and cross browser compatibility, i kind of stumbled on this overlay style from javascript being applied to last item of ul by default, even before on clicking it.
    So wat happens is when i click outside the main container, the overlay of last item comes to view.. it hides all the elements below.

    you can find this in the demo wen u click on the white space, a few pixel below last element(sydney) or just below ‘back to codrops article’.

    This appears only in IE… I am using IE9. when i checked with developer tools, last overlay is styled with overlay.css from boxgrid.js by default.

    i can’t find a work around for this and would like have some advice on why this happens.

    Thank again for the awesome tutorial.

Comments are closed.