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

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.

Tagged with:

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 up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 74

Comments are closed.
  1. Metro Style! and the color combination! same style and colors am using in my website redesign! awesome 😀

  2. Metro Style and Colors are the same as the ones I’m using in my website redesign! what a coincidence X)

  3. Sorry thought I didn’t post my first comment, had to hard-refresh to see it was posted 😛

  4. why does firefox is lame never works smoothly with css and this tutorial not working with firefox

    • Works just fine for me, albeit an ever-so-slight jitter on the animation. What exactly isn’t working for you, and what version of Firefox are you running? Is it the demo that isn’t working for you, or have you tried coding this yourself?

  5. Wow, I’m ALWAYS impressed with your work. Very nice, thank you for giving me yet another good example to aspire to 🙂

  6. Great! What you you suggest if you needed a gracefull fallback on IE7-8 for this?

  7. Hello Sir, thanks for this awesome tut, since i am an engineering student and dont have any particular knowledge about this , i was trying to fit the expanded image in the container itself instead of going fullscreen , what i should do to do that please help , i tried modding the css but in vain , and i dont have a clue about javascript , but please can you spare some more time and teach me how to do this… thank you in advance

  8. Hello Sir,
    I am an engineering student from INDIA , I really wanna thank you for all your contributions on codrops , really they are awesome and you doing a great job there, well why i am writing to you is because , we have this magazine of our college and it is going online from the paper to INTERNET , and they asked me to come up with some idea for the design of the website , and frankly when i saw the todays article of yours on codrops using css clips i was amazed and really interested to use that in my design , but as soon as i tried that i found out that i aint able to limit the full screen image which we get on clicking into a container instead of going full screen , please can you help me with this…? please please…>!!!! o.O

  9. Awesome Tutorial like always; this is simply stunning and will be a pleasure wrapping my head around the next few days.

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

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

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

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

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

  15. 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.

  16. Great work Mary Lou!

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

    Thanks!

  17. 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.

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

  19. 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.

  20. 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.

  21. I reeeeeeally like this.
    It suits one of my landing page ideas perfectly.
    Thank you.

    • Or perhaps not quite yet — I gutted everything on the overlay layers except for the close button and replaced with my own content, but then I noticed the links in that content aren’t working when clicked. (They will if you right-click and open in a new window.) I do need links on the interior part, though — big part of why I was looking for a thing like this. Help? If that could get working, this would just be fantastic.

  22. Hi! Great stuff you got here! Just one question though, does it work if you want to make your content dynamic (ie pulling data from a json object and parsing it)? I tried doing it but its not working. Help anyone?

  23. hello
    great tutorial.
    i have a problem : i don’t succeed to make a link insite the open part ..

    Ex : i want to make a like on “contact” , its looks like a clic, the “a” and “a:hover” works, i see the link on the browser, but when i clic, nothing appened :

    my texte
    Contactez-nous

    any idea ?
    thanks

    • I think I found a solution to make links work in the overlay.

      In the boxgrid.js file (around line 94), change pointerEvents:’auto’ to…pointerEvents:’none’

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

      Seems to be working so far.

    • add this code around line 94

      this will enable all links

      $(‘a’).click(function(){
      var getHref = $(this).attr(“href”);
      window.location.href = getHref ;
      });

  24. Same problem. This is working beautifully, but I need links to work on the overlay. Why are they working only when I right-click? So mysterious!

    Help would be appreciated. Thanks for everything:)

  25. The box grid JS file disables links in the lay over box, and this is so to prevent the links beneath the lay over from being accidentally clicked and messing up everything. (Perhaps a little backface visibility approach could have fixed this but I’m not sure.)

    Anyways, to enable links in the lay over box (if you’re considering using it for a nice UI effect)
    You will need an event handler. And below is the proper syntax for it:

    a style=”cursor:pointer;” onclick=”window.open(‘http://www.google.com’,’_self’);”

    Hope this helps.

  26. add this to your js

    $(‘.divclass a’).click(function(e){
    e.preventDefault();
    window.location = $(this).attr(‘href’);
    });

    .divclass should be the classe of the div after the over lay and before the links

  27. Hi I tried to add a youtube video like so
    <ul id="rb-grid" class="rb-grid clearfix"> <li class="icon-clima-2"> <h3>Paris</h3><span class="rb-temp">11°C</span> <div class="rb-overlay"> <span class="rb-close">close</span> <iframe src="//www.youtube.com/embed/ZzV8NBHu2TY" width="960" height="720" frameborder="0" allowfullscreen></iframe> </div> </li>
    but it didnt work at all is there a way to make it work with videos?