The beautiful iPhone mockup used in the demo is by Jakub Kejha
Let’s get started with the markup.
The Markup
The HTML will consist of a main wrapper that contains a heading, a division for the device and a division for the grid:
<div id="ac-wrapper" class="ac-wrapper"> <h2>Weatherous <span>Concept & UI Design</span></h2> <div class="ac-device"> <a href="#"><img src="images/screen1.jpg"/></a> <h3 class="ac-title">Gentrify small batch umami retro vegan</h3> </div> <div class="ac-grid"> <a href="#"><img src="images/screen1.jpg"/><span>Gentrify small batch umami retro vegan</span></a> <a href="#"><img src="images/screen2.jpg"/><span>Chambray squid semiotics</span></a> <a href="#"><img src="images/screen3.jpg"/><span>Fashion axe blue bottle</span></a> <a href="#"><img src="images/screen4.jpg"/><span>Photo booth single-origin coffee</span></a> <a href="#"><img src="images/screen5.jpg"/><span>Flexitarian synth keytar blog master</span></a> <a href="#"><img src="images/screen6.jpg"/><span>Next level retro flexitarian freegan</span></a> <a href="#"><img src="images/screen7.jpg"/><span>Pour-over superious meggings terry</span></a> <a href="#"><img src="images/screen8.jpg"/><span>Seitan william vinyl chillwave</span></a> </div> </div>
Once we click on a grid item, we will update the content of the device container. We will also make the span for each grid item appear on hover.
Let’s style everything.
The CSS
Note that the CSS will not contain any vendor prefixes, but you will find them in the files.
In this tutorial we will be going through the style of demo 1.
So let’s start with the main wrapper. This will be the container that will have perspective. The origin will not be in the center but a bit more up:
.ac-wrapper { width: 100%; position: relative; perspective: 1000px; perspective-origin: 50% 25%; }
The heading will be positioned absolutely on the left side of the device:
.ac-wrapper h2 { top: 20%; width: 50%; position: absolute; font-weight: 300; font-size: 4em; text-align: right; padding: 0 180px 0 50px; }
Let’s give a slightly different look to the span:
.ac-wrapper h2 span { display: block; font-size: 60%; color: #c0c0c0; }
The device will have the iPhone mockup as a background image and we will set the right dimensions. This container will need to preserve the 3D transforms and we’ll add a transition to it. Later, we’ll define a “transition classes” that will contain the properties for the altered states.
.ac-device { background: url(../images/iPhone.png) no-repeat; width: 288px; height: 611px; margin: 0 auto; position: relative; transition: all 0.3s ease; transform-style: preserve-3d; }
The screenshot will be inside of an anchor and we’ll set the dimensions here and position it to fit into the mockup:
.ac-device a { height: 440px; width: 249px; display: inline-block; margin: 85px 0 0 20px; } .ac-device a img { display: block; }
The title for each screenshot once it’s in the mockup view will be positioned absolutely on the right side of the device:
.ac-device h3 { position: absolute; font-size: 2.5em; left: 100%; width: 100%; top: 60%; margin-left: 30px; font-weight: 300; color: #888; }
Now, let’s style the grid. We want to display a total of eight items so a row will have four items. Let’s set a fitting width, make it absolute and center it by setting a negative left margin (half of its width) and a left value of 50%. The initial opacity is 0 and since the grid is displayed and covering the device, we’ll set the pointer events to none
so that we can’t click on it when it’s invisible. We’ll also add a transition and translate it -350px on the Z axis:
.ac-grid { position: absolute; width: 620px; left: 50%; margin-left: -310px; height: 100%; z-index: 1000; top: 0; opacity: 0; pointer-events: none; transform-style: preserve-3d; transition: all 0.3s ease; transform: translateZ(-350px); }
The anchors in the grid will be floated left and the images inside will be set to 100% width. This will come in handy later on when we apply some media queries:
.ac-grid a { width: 145px; display: block; position: relative; float: left; margin: 10px 5px; cursor: pointer; } .ac-grid a img { display: block; width: 100%; }
The span for the description will be positioned absolutely on top of the anchor and we’ll fade it in and move it a bit on hover:
.ac-grid a span { position: absolute; height: 100%; width: 100%; left: 0; top: 0; text-transform: uppercase; padding: 3em 1em 0; z-index: 100; color: #ddd; background: rgba(0,0,0,0.4); font-weight: 700; opacity: 0; transform: translateY(-5px); transition: all 0.2s ease; } .ac-grid a:hover span { opacity: 1; transform: translateY(0); }
Next, we’ll define the “transition classes”. When we click on the device, we’ll apply a class to the wrapper which will trigger the fading in and scaling up of the grid and the moving back of the device:
.ac-wrapper.ac-gridview .ac-device { transform: translateZ(-350px); opacity: 0.6; } .ac-wrapper.ac-gridview .ac-grid { transform: translateZ(0px); opacity: 1; pointer-events: auto; }
Once the grid is there, we also set the pointer-events to auto
again.
Our layout has some absolutely positioned elements and we’ll need to take care of them on smaller screens. The idea is that we will switch the main heading to the right side first and then center everything once the screen is very small. The second media query takes care of the grid structure. Here we will set a fluid width for both, the grid and the anchors:
@media screen and (max-width: 63.875em) { .ac-wrapper { font-size: 60%; width: 100%; padding: 0 20px; } .ac-device { margin: 0; width: 100%; } .ac-device h3 { width: 50%; left: 290px; } .ac-wrapper h2 { left: 308px; padding: 0; text-align: left; margin-left: 30px; } } @media screen and (max-width: 39.8125em) { .ac-grid { width: 90%; left: 5%; margin-left: 0; padding-top: 150px; } .ac-grid a { width: 22%; } } @media screen and (max-width: 35.6875em) { .ac-wrapper { padding: 0 20px 100px; } .ac-wrapper h2 { width: 100%; text-align: center; margin: 0 0 1em; top: 0; left: auto; position: relative; } .ac-device { margin: 0 auto; width: 288px; } .ac-device h3 { position: relative; margin: 0; left: auto; width: 100%; top: 100px; display: block; text-align: center; } }
And that’s all the style! Let’s take a look at the JavaScript.
The JavaScript
Let’s start by caching some elements and initialize some variables:
var $ el = $ ( '#ac-wrapper' ), // device element $ device = $ el.find( '.ac-device' ), // the device image wrapper $ trigger = $ device.children( 'a:first' ), // the screens $ screens = $ el.find( '.ac-grid > a' ), // the device screen image $ screenImg = $ device.find( 'img' ), // the device screen title $ screenTitle = $ device.find( '.ac-title' ), // HTML Body element $ body = $ ( 'body' );
We will bind the events to the device’s image wrapper (anchor) and to the screen elements.
function init() { // show grid $ trigger.on( 'click', showGrid ); // when a grid´s screen is clicked, show the respective image on the device $ screens.on( 'click', function() { showScreen( $ ( this ) ); return false; } ); }
When the device’s image is clicked, the grid is shown. For this to happen the class “ac-gridview” has to be added to the ac-wrapper element:
function showGrid() { $ el.addClass( 'ac-gridview' ); // clicking somewhere else on the page closes the grid view $ body.off( 'click' ).on( 'click', function() { showScreen(); } ); return false; }
When a screen element is clicked we remove the “ac-gridview” class from the ac-wrapper element, and update both, image source and title on the respective elements:
function showScreen( $ screen ) { $ el.removeClass( 'ac-gridview' ); if( $ screen ) { // update image and title on the device $ screenImg.attr( 'src', $ screen.find( 'img' ).attr( 'src' ) ); $ screenTitle.text( $ screen.find( 'span' ).text() ); } }
For the third demo we also want to offer the possibility to navigate through the screenshots without having to open the grid. Depending to the direction we are navigating, the next screen will either scale up / fade in (navigating to the next screen) or move up / fade in (navigating to the previous screen). The same logic applies to the current screenshot. In order for this to work, we need to add the next/previous screen’s image to the DOM right before the current screen’s image (both images being absolute). When the transition ends we remove the old one:
function navigate( direction ) { // if currently animating return if( animating ) { return false; } animating = true; // update current if( direction === 'next' ) { current = current 0 ? --current : screensCount - 1; } // next screen to show var $ nextScreen = $ screens.eq( current ); // if css transitions support: if( support ) { // append new image to the device and set the transition and initial style var $ nextScreenImg = $ ( '' ).css( { transition : 'all 0.5s ease', opacity : 0, transform : direction === 'next' ? 'scale(0.9)' : 'translateY(100px)' } ).insertBefore( $ screenImg ); // update title $ screenTitle.text( $ nextScreen.find( 'span' ).text() ); setTimeout( function() { // current image fades out / new image fades in $ screenImg.css( { opacity : 0, transform : direction === 'next' ? 'translateY(100px)' : 'scale(0.9)' } ).on( transEndEventName, function() { $ ( this ).remove(); } ); $ nextScreenImg.css( { opacity : 1, transform : direction === 'next' ? 'scale(1)' : 'translateY(0px)' } ).on( transEndEventName, function() { $ screenImg = $ ( this ).off( transEndEventName ); animating = false; } ); }, 25 ); } else { // update image and title on the device $ screenImg.attr( 'src', $ nextScreen.find( 'img' ).attr( 'src' ) ); $ screenTitle.text( $ nextScreen.find( 'span' ).text() ); animating = false; } }
This navigation concept was introduced by Evan You and you can check it out here, or watch the video.
And that’s all! We hope you enjoyed this tutorial and find it inspiring!