CSS3 Hourglass throbber

Multiple solutions for image-less animated throbbers float around the web. But  have you ever felt nostalgic for the good old hourglass? I will show you how to create a css-only  rotating hourglass. You can even see the sand flowing.

Ingredients

  • 4 html elements
  • 3 animation keyframes
  • background gradients
  • border radius to taste

Look mom, no Javascript! It requires fairly up-to-date WebKit browser, Firefox or IE10.

Result

Directions

The hourglass model above consists of four pieces:

The tricky part first – two bulbs resembling droplets need to be connected together through a thin neck. Here we have to trade off realistic appearance for simple solution. Each bulb is modeled as a rectangle. Since CSS3 allows to assign border radius to certain corners, I rounded the top left and top right corners. Thus giving the bulbs somewhat oval shape. The neck is actually the borders of the two bulbs overlapping in the middle, avoiding the addition of an extra element.

For the sand I went with two elements (one for each bulb) animated in a way such as to give the impression of fixed amount of sand flowing between them.  While the hourglass rotates the sand is flowing in two directions –  filling up the bulb and pouring out of it. As you may have guessed, we could animate the height and top properties. But since the sand elements are also rounded to match the bulb’s shape, changing the height would break that shape.

I decided to go with another solution – giving the elements gradient background and animating the background position instead:

Hourglass gradient animation

 

This leaves me with a smooth, infinitely looping animation.

The symmetry between the two bulbs lets us reuse the same styling and animation. I flipped vertically the second pair of bulb + sand using transform: rotate(). But now the sand fills up in both bulbs simultaneously. This can be fixed by delaying the animation sequence of one of the elements with half a cycle (see figure above)  – while one bulb is filling up, the other is emptying.

To make my hourglass more realistic, I added a stream of sand. It’s a single rectangle half the height of the hourglass, re-positioned after each rotation and fading in and out.

Finally, the stands of the hourglass are key elements. They give it visually more volume and mass. I added them as two rectangles, slightly sticking out of the frame. Another option would be to play around with the top and bottom border of the parent element (div.hourglass).

Putting it all together

The hardest part was syncing up all animations to make them look seamless, easing each animation state into the other. One full rotation takes 8 seconds to complete. The simplest fallback for older browsers would be a GIF animation looping as background of the div.hourglass element.

HTML:

<div class="hourglass">
	<div class="hourglass-stand"></div>
	<div class="hourglass-sand"></div>
	<div class="hourglass-stream"></div>
</div>

CSS:

@keyframes hourglass-spin { 
	0% { transform: rotate(0deg); }
	10% { transform: rotate(180deg); }
	50% { transform: rotate(180deg); }
	60% { transform: rotate(360deg); }
	100% { transform: rotate(360deg); }
}
@keyframes hourglass-sand {
	0% { background-position: 0 25px; }
	50% { background-position: 0 0; }
	100% { background-position: 0 -25px; }
}
@keyframes hourglass-stream {
	0% { top: 29px; opacity: 0; }
	10% { opacity: 1; }
	49.9% { top: 29px; opacity: 1; }
	50% { top: 2px; opacity: 0;}
	60% { opacity: 1;}
	100% { top: 2px; opacity: 1; }
}

/* WebKit */

@-webkit-keyframes hourglass-spin { 
	0% { -webkit-transform: rotate(0deg); }
	10% { -webkit-transform: rotate(180deg); }
	50% { -webkit-transform: rotate(180deg); }
	60% { -webkit-transform: rotate(360deg); }
	100% { -webkit-transform: rotate(360deg); }
}
@-webkit-keyframes hourglass-sand {
	0% { background-position: 0 25px; }
	50% { background-position: 0 0; }
	100% { background-position: 0 -25px; }
}
@-webkit-keyframes hourglass-stream {
	0% { top: 29px; opacity: 0; }
	10% { opacity: 1; }
	49.9% { top: 29px; opacity: 1; }
	50% { top: 2px; opacity: 0;}
	60% { opacity: 1;}
	100% { top: 2px; opacity: 1; }
}

/* All other browsers */

.hourglass { 
	position: relative; 
	width: 28px;
	height: 56px;
	animation: hourglass-spin 8s ease-in-out 4s infinite;
	-webkit-animation: hourglass-spin 8s ease-in-out 4s infinite;
} 

.hourglass::before, .hourglass::after { 
	position: absolute; 
	content: ''; 
	left: 0;  
	bottom: 0;
	width: 24px;
	height: 25px;
	background: #000;
	border: 2px solid #000;
	border-top-left-radius: 15px 25px; 
	border-top-right-radius: 15px 25px; 
}

.hourglass::before { 
	top: 0; 
	transform: rotate(180deg);
	-webkit-transform: rotate(180deg);
}

.hourglass .hourglass-stand::after, .hourglass .hourglass-stand::before {
	position: absolute;
	content: '';
	z-index: 3;
	left: -4px;
	bottom: 0;
	width: 36px;
	height: 5px;
	background: #000;
}

.hourglass .hourglass-stand::before {
	top: 0;
}

.hourglass .hourglass-sand::before, .hourglass .hourglass-sand::after { 
	position: absolute;
	content: ''; 
	z-index: 2; 
	left: 2px;
	bottom: 2px;
	width: 24px;
	height: 25px;
	background: linear-gradient(#CF9751 0%,#CF9751 50%,#fff 50%,#ddd 100%);
	background-size: 30px 50px;
	border-top-left-radius: 13px 23px; 
	border-top-right-radius: 13px 23px;
	animation: hourglass-sand 8s ease-in-out 0s infinite;  
	-webkit-animation: hourglass-sand 8s ease-in-out 0s infinite; 
}

.hourglass .hourglass-sand::before { 
	top: 2px; 
	transform: rotate(180deg); 
	animation-delay: -4s; 
	-webkit-transform: rotate(180deg); 
	-webkit-animation-delay: -4s; 
}

.hourglass .hourglass-stream { 
	position: absolute;  
	z-index: 2;
	left: 13px;
	width: 2px;
	height: 25px;
	opacity: 0;
	background: #CF9751;
	animation: hourglass-stream 8s linear 0.6s infinite;
	-webkit-animation: hourglass-stream 8s linear 0.6s infinite;
}

Feel free to submit your thoughts and improvements in the comments below.

Leave a Reply