Forums » HTML & HTML 5

Efeito de Sobreposição Ampliada com CSS3

    • 11 posts
    20 de fevereiro de 2015 10:18:55 ART

    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:


    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:


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

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

    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( $ 'isExpanded' ) ) {
    				return false;
    			$ 'isExpanded', true );
    			// save current index of the item
    			current = $item.index();
    			var layoutProp = getItemLayoutProp( $item ),
    				clipPropFirst = 'rect(' + + 'px ' + ( layoutProp.left + layoutProp.width ) + 'px ' + ( + 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() {
    					$ transEndEventName );
    					setTimeout( function() {
    						$overlay.css( 'clip', clipPropLast ).on( transEndEventName, function() {
    							$ 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 : - 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(' + + 'px ' + ( layoutProp.left + layoutProp.width ) + 'px ' + ( + 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() {
    					$ transEndEventName );
    					setTimeout( function() {
    						$overlay.css( 'opacity', 0 ).on( transEndEventName, function() {
    							$ transEndEventName ).css( { clip : clipPropLast, zIndex: -1 } );
    							$ 'isExpanded', false );
    						} );
    					}, 25 );
    				} );
    			else {
    				$overlay.css( 'z-index', -1 );
    				$ '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.