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

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;
}

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

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.

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 74

Comments are closed.
  1. Dear,

    I’m trying to use this fantastic design for a basis of a new website.

    For Aesthetic purposes the background of the elements is made transparent.
    Only problem is that I now need to ‘hide’ one of the boxes upon click.

    I tried using onclick in the indexhtml file but couldnt get it to work.
    Can someone help me what code I should use?

  2. I used this recently in a project at work and it works great but we discovered that on a PC using VIsta 64-bit IE 8 it does not work, well, it does but it doesn’t. It displays over the page as the page loads (which it is not suppose to do) then after I click the close X, I go to click on anything on the page and he overlay returns, preventing me from interacting at with the page. So far it works correctly in CHROME, SAFARI and FIREFOX on PC and MAC but IE 8 on Visa 64-bit PC does not work at all.

    Please let me know what browsers this effect does not work on.

  3. Hi! I like this a lot! I’m thinking of creating a website following this design. So.. how do I do that? Assuming that the index page will be the one with boxes. Then the web pages will be the expanded box. To make it clearer, the boxes with the names of the places will be my “home page”, and the boxes will serve as the menu. the names of the places will be replaced with Home, About Us, Contact, etc. Then, the boxes when clicked will enlarge, just like the effect in this tutorial. But instead of displaying the weather forecast, the expanded page will serve as the website content page (like an ordinary website). How do I do that? Btw, I’m a newbie so I’m only familiar with html and css using dreamweaver. I hope someone can teach me! thank you in advance!

  4. hello folks,

    Which grid system is this demo 1? Grid, foundantion…??!?! Please someone can help me? Thank you

  5. I was searching for a way to open links on pop-up page in new window.
    This is what I learned:

    In boxgrid.js: after
    item.on( ‘click’, funtion() … … } );
    and before
    if( supportTransitions ) { ……..

    I added this code:

    $('.div-class-with-links-and-text a').click(function (e) { e.preventDefault(); window.open (this.href, '_blank'); });

  6. Is it possible to scroll the overlay content?
    Could anyone helps me out with this issue? This is really a fantastic design! It will be more awesome if the I could put content more than one page in it!!

  7. Hi, i like this effect so much. However the transition seen to start from the top left of the screen on safari. Cheers!

  8. hi,, this is so good effect.. i like this effect.. but i want that, onclick it’s open with a different url.