Advertisement

Bringing Our Behance Portfolio Alive With CSS Animation

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →
This post is part of a series called Build Your Own Behance-Powered Portfolio.
Styling Our Behance Portfolio Website Using LESS

In previous tutorials we've looked into harnessing the Behance API to drive our own web page, and then, using LESS, we made the whole thing look presentable. In this tutorials we're going to enhance the experience for visitors by adding a lightbox effect and some CSS animations.

Lightbox and Animation Effects

Many portfolio websites today employ a lightbox of some kind for their portfolio images. In this tutorial, we will apply the same to our website. The image cover will zoom-in when the users click on it, along with the other images that are in the content, so the users will be able to see each image therein more closely. 

Here are the tools we need to accomplish this:

Magnific Popup

We're going to rely on a jQuery Lightbox plugin called Magnific Popup by Dmitry Semenov. It's lightweight in size, fast, and responsive — just the way we want it.

Animate.css

We will also incorporate CSS3 animation to help our website come alive. We'll be adopting a few snippets from Animate.css, which provides a tremendous collection of CSS3 animation through a number of drop-in classes to apply the animation immediately.

Integrating Magnific Popup

Let's start by adding the Magnific Popup stylesheet to the head tag. 

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/0.9.9/magnific-popup.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundicons/3.0.0/foundation-icons.min.css">
<link href='http://fonts.googleapis.com/css?family=Cantata+One|Open+Sans:300,600' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="css/style.css">

Then we'll add the scripts lower down the page in the footer, giving the page a chance to render before the behavioral scripts are loaded.

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/magnific-popup.js/0.9.9/jquery.magnific-popup.min.js"></script>

Then, we will need to add an HTML5 data attribute, data-project-id="{{this.id}}" to the figure element that wraps the portfolio cover image, as follows.

...
<figure class="portfolio-cover" title="{{this.name}}" data-project-id="{{this.id}}">
	{{#if this.covers.[404]}}
	<img class="portfolio-image" src="{{this.covers.[404]}}" alt="">
	{{else}}
		{{#if this.covers.[230]}}
		<img class="portfolio-image" src="{{this.covers.[230]}}" alt="">
		{{else}}
		<img class="portfolio-image" src="{{this.covers.[202]}}" alt="">
		{{/if}}
	{{/if}}
</figure>
...

Refresh the website and inspect the website through Chrome DevTools or Firebug. You should find the data-project-id contains the ID number of the portfolio, as seen below.

The data-project-id in figure element with the ID number
 

We will use the data attribute to retrieve the content of the selected portfolio with that ID assigned later on.

After that, we will also need to change the cursor appearance to zoom-in, as follows:

 ...
.portfolio-cover {
 	cursor: pointer;
 	cursor: -webkit-zoom-in;
 	cursor: -moz-zoom-in;
 	cursor: zoom-in;
 	width: 100%;
}
...

The zoom-in cursor will suggest that the image is zoom-able; the user should expect that they can click the image. However, the zoom-in value is not yet supported in any version of Internet Explorer, according to MDN. So that's why we've also specified the cursor to pointer prior to cursor: zoom-in as the fallback for Internet Explorer as well as the other browsers that may not support it.

Making it Work

Now, we will add the script to initialize Magnific Popup. Since the plan is not only to show the portfolio cover image, but also bring the other images in the content, the script might look a bit hefty. So here we will add the script sequentially. The first thing we will write is the jQuery .on('click') method. We will only execute Magnific Popup when the user clicks on the cover image.

$('#portfolio').on('click', '.portfolio-cover', function() {
	//the rest of the script goes here...
}

Then we will define the following variables:

  • $this, the this variable refers to the object bound to the .on() method.
  • projectID will contain $this.data('project-id') which grabs that ID number from the data-project-id attribute. We will use this ID to retrieve the content through the Behance API.
  • beProjectContentAPI will contain the Behance API endpoints to retrieve the Behance project content.
  • keyName, this variable forms the key name that we will be using for storing the data retrieved from Behance in localStorage. The name format will be behanceProjectImages- then followed by the project ID number. Contrary to what we have done previously, we now use localStorage to store the data instead of using sessionStorage. The reason being that we assume that Behance users would rarely update the content once it has been published. So, in this case, we are better to use localStorage, as it will store the data persistently; the data will remain in the browser as long as we do not intentionally delete it.
$('#portfolio').on('click', '.portfolio-cover', function() {
	var $this = $(this),
	projectID = $this.data('project-id'),
	beProjectContentAPI = 'http://www.behance.net/v2/projects/'+ projectID +'?callback=?&api_key=' + apiKey,
	keyName = 'behanceProjectImages-' + projectID;
}

Then we create the main function that will execute Maginific Popup. We will name this function showGallery(). We will also apply the following options in Magnific Popup:

  • items; the items are very important here. This will contain the list of images that will be displayed in the Lightbox.
  • gallery; when we enable gallery, Magnific Popup will adds arrows to navigate through each image therein.
  • type; we will use image for the only content type allowed in the Lightbox.

This last piece is inevitable; we will have to add .magnificPopup('open') so it opens the Lightbox immediately after initialization.

$('#portfolio').on('click', '.portfolio-cover', function() {
	var $this =	$(this),
		projectID = $this.data('project-id'),
		beProjectContentAPI = 'http://www.behance.net/v2/projects/'+ projectID +'?callback=?&api_key=' + apiKey,
		keyName = 'behanceProjectImages-' + projectID;
	
	function showGallery(dataSource) {	
		$this.magnificPopup({
			items: dataSource,
			gallery: {
				enabled: true
			},
			type: 'image'
		}).magnificPopup('open');
	};
}

We will only execute showGallery() under certain conditions; if the data for the selected portfolio is available in localStorage, go get it and execute the showGallery(), otherwise get the data from the API with $.getJSON() first, then execute showGallery() and store the data in localStorage for use in the future. As we did previously, we have to use JSON.stringify() to convert the data into a string so it can be saved in localStorage, then we will use JSON.parse() to format the data back to JSON.

Checking for Images

The thing that we have to note here is that the content retrieved from the API could be video, embedded video, or text, which are not allowed; we only accept the image content type. Thus, before posting the data to localStorage, we need to add the following piece of code to filter the content.

var src = [];
$.each(projectContent.project.modules, function(index, mod) {
	if(mod.src != undefined) {
		src.push({ src: mod.src });	
	}
});

Here is the whole script, at the end.

$('#portfolio').on('click', '.portfolio-cover', function() {
	var $this =	$(this),
		projectID = $this.data('project-id'),
		beProjectContentAPI = 'http://www.behance.net/v2/projects/'+ projectID +'?callback=?&api_key=' + apiKey,
		keyName = 'behanceProjectImages-' + projectID;
	
	function showGallery(dataSource) {	
		$this.magnificPopup({
			items: dataSource,
			gallery: {
				enabled: true
			},
			type: 'image'
		}).magnificPopup('open');
	};

	if(localStorage.getItem(keyName)) {
		var srcItems = JSON.parse(localStorage.getItem(keyName));
		showGallery(srcItems);
	} else {
		$.getJSON(beProjectContentAPI, function(projectContent) {
			var src = [];
			$.each(projectContent.project.modules, function(index, mod) {
				if(mod.src != undefined) {
					src.push({ src: mod.src });	
				}
			});
			showGallery(src);
			var data = JSON.stringify(src);
			localStorage.setItem(keyName, data);
		});
	};
});

Now, when you click the image, it should zoom-in and be displayed in Lightbox fashion:

If you inspect the website with Chrome DevTools, you should now find the content is stored in localStorage.

Furthermore, you can navigate through all images in the content using the arrows. But the transition currently feels pretty awkward (right?); it jumps from one image to another instantaneously. So let's make it smoother and more communicative with some animation, shall we?

Integrating Animate.css

First of all, we will need to add mainClass: 'animated' and removalDelay: 350 to our magnificPopup function.

 ... 
function showGallery(dataSource) {	
	$this.magnificPopup({
		items: dataSource,
		gallery: {
			enabled: true
		},
		type: 'image',
		mainClass: 'animated',
		removalDelay: 350
	}).magnificPopup('open');
};
...

In this code, we added a new class named animated to the Lightbox. The animated class is the class used in Animate.css to designate animation for an element. This class would also be useful to enable or disable the animation as you like; if you want to disable it simply remove the mainClass: 'animated', line.

We also added removalDelay, which specifies the duration before the Lightbox is removed from the DOM completely. The delay herein will give the animation some time of visibility.

Adapting Keyframe Styles

Next, we will adopt a few CSS Keyframes, Transforms, and Transitions that form the animation effect in Animate.css. We will convert them into LESS format using LESSHat.

Let's start with the Keyframes.

.keyframes(~'fadeInRight, 0% { transform: translateX(20px); opacity: 0; } 100% { transform: translateX(0); opacity: 1; }');
.keyframes(~'fadeInLeft, 0% { transform: translateX(-20px); opacity: 0; } 100% { transform: translateX(0); opacity: 1; }');

.keyframes(~'fadeOutRight, 0% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(20px); opacity: 0; }');
.keyframes(~'fadeOutLeft, 0% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(-20px); opacity: 0; }');

.keyframes(~'fadeInDown, 0% { transform: translateY(-20px); opacity: 0; } 100% { transform: translateY(0); opacity: 1; }');
.keyframes(~'fadeOutDown, 0% { transform: translateY(0); opacity: 1; } 100% { transform: translateY(20px); opacity: 0; }');

We've added several Keyframes named fadeInRight, fadeInLeft, fadeOutRight, fadeOutLeft, fadeInDown, and fadeOutDown which have been translated into LESS format with LESSHat .keyframes() Mixins.

There are several parts within the Lightbox that we will animate, namely: the background overlay that covers the entire viewport, the Lightbox content or image, and the navigation arrows.

The overlay background animation is fairly simple. It won't need those Keyframes above, at all, it will simply fade-in when the Lightbox shows up and fade-out when it disappears. Here are all the style rules to achieve that animation.

.mfp-bg.animated {
    opacity: 0;
    .transition(opacity 350ms ease-out);
}
.mfp-bg.mfp-ready.animated {
    opacity: 0.8;
}
.mfp-bg.mfp-removing.animated {
    opacity: 0;
}

In Magnific Popup, the overlay background is given a class named mfp-bg. In this code, we set its opacity to 0 so it will initially be invisible, and also set the Transition duration for the opacity property.

Furthermore, Magnific Popup will produce a set of new classes for targeting different states; for instance, when the Lightbox is fully shown, it will add the mfp-ready class. In this state, we've set the opacity to 0.8. As we've set the Transition, it will give us the animation effect; the opacity will transmit from 0 to 0.8 in 350ms. 

Then when the Lightbox disappears, Magnific Popup will output the mfp-removing class. In this state, we set its opacity back to 0, making the overlay background invisible again.

Below is the style rules that animate the Lightbox content.

.mfp-wrap.animated .mfp-content {
    .animation-duration(350ms);
}
.mfp-wrap.animated .mfp-content {
    .animation-name(fadeInDown);
}
.mfp-wrap.mfp-removing.animated .mfp-content {
    .animation-name(fadeOutDown);
}

Like the overlay background, we also set the Transition duration for the content at 350ms. We also apply the Keyframes with .animation-name() Mixins. Here we've set the content to fade-in and at the same time slide down when it appears, then slide down and fade-out when it disappears.

Animating the Navigation Arrows

Lastly, we will add the animation for our Lightbox arrows.

.mfp-wrap.animated .mfp-arrow {
    .animation-duration(350ms);
}
.mfp-wrap.animated .mfp-arrow-left {
    .animation-name(fadeInRight);
}
.mfp-wrap.mfp-removing.animated .mfp-arrow-left {
    .animation-name(fadeOutLeft);
}
.mfp-wrap.animated .mfp-arrow-right {
    .animation-name(fadeInLeft);
}
.mfp-wrap.mfp-removing.animated .mfp-arrow-right {
    .animation-name(fadeOutRight);
}

The code here is quite similar to the snippet which animates the content. Here the left arrow will fade-in and slide from the right when it shows up, then slide to the left and fade out when it disappears The right arrow will simply do the opposite.

Conclusion

It's been a very long tutorial series! We succesfully built a functioning personal portfolio website from the ground with the Behance API as the data source. To build our website, we've also utilized a number of modern tools such as LESS, HandlebarsJS, and Animate.css. It's pretty easy to deploy the website, since it is only a static HTML - in fact, our demo is hosted as a GitHub static page. Alternatively, you can upload it using FTP to a web server.

If you want to take this project further, you can add, for example, a "filter" that will sort the portfolio according to its creative field. You could also add nice hover effects. In any case, I hope you enjoyed the series, and learned a couple of tricks that you can adopt in your own website.

Advertisement