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

    Metro Style! and the color combination! same style and colors am using in my website redesign! awesome :D

  2. 3

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

  3. 4

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

  4. 9

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

    • 10

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

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

  6. 16

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

  7. 17

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

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

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

Comments are closed.