Squeezebox Portfolio Template

Go to Source

An intro block that slides out to uncover a gallery of portfolio items.

We’ve been experimenting with some motion effects to build a simple portfolio template. The idea is to show a gallery of projects as a separate, secondary module, with the first block still partially visible – just one click away.

This is a UX pattern we’re used to in mobile apps: if you tap on an element and it slides out, but not entirely, you know you can tap on it to bring it back. The challenge here was to make this work on bigger devices too.

Here is a quick animation that shows the flow of the resource:


This resource was inspired by this dribbble shot by the talented Javi Pérez.

Creating the structure

The HTML structure is composed by 3 main blocks: a .cd-intro-block element, containing the action button to reveal the projects slider, an unordered list (.cd-slider), which is the projects gallery/slider, and a .cd-project-content element with the single project.

<div class="cd-intro-block">
	<div class="content-wrapper">
		<h1>Squeezebox Portfolio Template</h1>
		<a href="#0" class='cd-btn' data-action="load-projects">Show projects</a>
</div> <!-- .cd-intro-block -->

<div class="cd-projects-wrapper">
	<ul class="cd-slider">
		<li class="current">
			<a href="#0">
				<img src="img/img.png" alt="project image">
				<div class="project-info">
					<h2>Project 1</h2>
					<p>Lorem ipsum dolor sit amet.</p>

			<!-- project preview here -->

		<!-- other projects here -->

	<ul class="cd-slider-navigation cd-img-replace">
		<li><a href="#0" class="prev inactive">Prev</a></li>
		<li><a href="#0" class="next">Next</a></li>
	</ul> <!-- .cd-slider-navigation -->
</div> <!-- .cd-projects-wrapper -->

<div class="cd-project-content">
		<h2>Project title here</h2>
		<em>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt, ullam.</em>
		<!-- other content here -->
	<a href="#0" class="close cd-img-replace">Close</a>
</div> <!-- .cd-project-content -->

Adding style

When user clicks the a[data-action="show-projects"], the .projects-visible class is added to the .cd-intro-block and the .cd-projects-wrapper: this class triggers the intro section animation and the projects slider entrance.
The .cd-intro-block is translated (along the Y axis) by 90%, while the .cd-projects-wrapper visibility is set to visible.

.cd-intro-block {
  transition: transform 0.5s;
  transition-timing-function: cubic-bezier(0.67, 0.15, 0.83, 0.83);
.cd-intro-block.projects-visible {
  /* translate the .cd-intro-block element to reveal the projects slider */
  transform: translateY(-90%);
  box-shadow: 0 4px 40px rgba(0, 0, 0, 0.6);
  cursor: pointer;

.cd-projects-wrapper {
  position: fixed;
  z-index: 1;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  visibility: hidden;
  transition: visibility 0s 0.5s;
.cd-projects-wrapper.projects-visible {
  visibility: visible;
  transition: visibility 0s 0s;

For the project slides entrance animation, the .slides-in class is added to each project item (with a delay of 50ms); this class triggers a different animation according to the screen size.
On mobile devices, each list item has, by default, width: 100% and opacity: 0; when the .slides-in class is added, the cd-translate animation is applied:

.cd-slider li {
  opacity: 0;
.cd-slider li.slides-in {
  opacity: 1;
  animation: cd-translate 0.5s;
@keyframes cd-translate {
  0% {
    opacity: 0;
    transform: translateY(100px);
  100% {
    opacity: 1;
    transform: translateY(0);

On desktop devices (viewport width more than 900px), each list item has, by default, width: 26%,  translateX: 400% and a rotate: -10deg; when the .slides-in class is added, each project is moved back to its original position (translateX:0) and rotated to 0 degree:

@media only screen and (min-width: 900px) {
  .cd-slider li {
    position: relative;
    float: left;
    width: 26%;
    top: 50%;
    transform: translateX(400%) translateY(-50%) rotate(-10deg);
    transition: opacity 0s 0.3s, transform 0s 0.3s;
  .cd-slider li.slides-in {
    /* reset style */
    animation: none;
    transform: translateY(-50%);

For the projects slider (desktop version only), all list items are in relative position, have a fixed width (26%) and a float: left; the .cd-slider total width is properly changed (using javascript – more in the Events handling section) so that all list items are placed on the same row.
When a user clicks the .next/.prev button, the .cd-slider list element is translated (to the left/right respectively) by an amount equal tree times a single list item width + its left margin.
To create the ‘squeezebox’ animation, each list item is also animated using the cd-slide-n animation:

@keyframes cd-slide-n {
  0%, 100% {
    transform: translateY(-50%);
  50% {
    transform: translateY(-50%) translateX(translateValue);

Basically, a different animation has been defined for each list item visible during the .cd-slider translation (a total of 6); each animation differs in the translateX value set in the 50% keyframe.

The following gif shows the movement of 3 visible elements when user clicks the .next button (the .cd-slider translation has been removed):


Events handling

On desktop devices, we change the .cd-slider width so that its <li> children stay on the same row; we used the setSliderContainer() function to set this width (and change it on resize); plus, on window resize, we update the .cd-slider translate value:

function setSliderContainer() {
	var mq = checkMQ(); //function to check mq value
	if ( mq == 'desktop' ) {
		var	slides = projectsSlider.children('li'), // projectsSlider = $('.cd-slider')
			slideWidth = slides.eq(0).width(),
			marginLeft = Number(projectsSlider.children('li').eq(1).css('margin-left').replace('px', '')),
			sliderWidth = ( slideWidth + marginLeft )*( slides.length ) + 'px',
			slideCurrentIndex = projectsSlider.children('li.current').index(); //index of the first visible slide
		projectsSlider.css('width', sliderWidth);
		//if the first visible slide is not the first <li> child, update the projectsSlider translate value
		( slideCurrentIndex != 0 ) && setTranslateValue(projectsSlider, ( slideCurrentIndex * (slideWidth + marginLeft) + 'px'));
	} else {
		//on mobile, reset style
		projectsSlider.css('width', '');
		setTranslateValue(projectsSlider, 0);
	resizing = false;
function setTranslateValue(item, translate) {
	    '-moz-transform': 'translateX(-' + translate + ')',
	    '-webkit-transform': 'translateX(-' + translate + ')',
		'-ms-transform': 'translateX(-' + translate + ')',
		'-o-transform': 'translateX(-' + translate + ')',
		'transform': 'translateX(-' + translate + ')',

var resizing = false;
$(window).on('resize', function(){
	//on resize - update projectsSlider width and translate value
	if( !resizing ) {
		resizing = true;

Besides, we used jQuery to add/remove classes (eg .projects-visible class when the user clicks the action button) and implement a basic slider navigation (next/prev buttons, keyboard and swipe navigation).
Go to Source

Originally posted 2015-09-04 11:27:18. Republished by Blog Post Promoter

A resource to understand how parallax scroll should works

A resource to understand how parallax scroll should works

There are many different ways to implement parallax. This  resource will let you to understand how parallax scroll should works and ho build it with css3



The parallax class is where the parallax magic happens. Defining the height and perspective style properties of an element will lock the perspective to its centre, creating a fixed origin 3D viewport. Setting overflow-y: auto will allow the content inside the element to scroll in the usual way, but now descendant elements will be rendered relative to the fixed perspective. This is the key to creating the parallax effect.

Next is the parallax__layer class. As the name suggests, it defines a layer of content to which the parallax effect will be applied; the element is pulled out of content flow and configured to fill the space of the container.

Finally we have the modifier classes parallax__layer--base and parallax__layer--back. These are used to determine the scrolling speed of a parallax element by translating it along the Z axis (moving it farther away, or closer to the viewport). For brevity I have only defined two layer speeds – we’ll add more later.

Since the parallax effect is created using 3D transforms, translating an element along the Z axis has a side effect – its effective size changes as we move it closer to or farther away from the viewport. To counter this we need to apply a scale() transform to the element so that it appears to be rendered at its original size:

The scale factor can be calculated with 1 + (translateZ * -1) / perspective. For example, if our viewport perspective is set to 1px and we translate an element -2pxalong the Z axis the correction scale factor would be 3: