From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
Today we’d like to show you how to create some pure CSS progress bars with a special 3D look. Consider this tutorial an advanced CSS exercise that will give you insight in a lot of interesting 3D properties and shading techniques. Creating UI components using only CSS will train your ability to think outside of the box and in this tutorial we will show you some tricks on how to create a more complex shape, use it as a progress bar and animate it.
transform-style: preserve-3d
, a crucial property for creating nested 3D structures; so the progress bars will be flat/not work in IE.
- ChromeSupported
- FirefoxSupported
- Internet ExplorerNot supported
- SafariSupported
- OperaSupported
We’ll make some use of Sass (together with Compass) in this tutorial, so make sure to get it set up and understand the basics:
If you’d like to use a full-fledged solution for animated progress bars, you should check out ProgressBar.js by Kimmo Brunfeldt or PACE by HubSpot for excellent page load progress bars.
For generating all the necessary prefixes you can use something like Autoprefixer or the plugin for Sublime Text.
We’ll use a lot of interesting CSS properties like transform, perspective and box-shadow. We’ll also make extensive use of SASS for saving plenty of time in generating the bars’ positions and skins. By using relative sizes (em, percentages), we’ll make sure that our progress bars are easily resizable.
Building the Faces
Let’s start by building a box which will contain all the six faces. This box will work as our main container setting the sizes of our bar and its view point. We’ll also use a wrapper for the perspective, and this wrapper’s font-size property will allow is to scale the progress bar with the help of some em
unit magic.
To make sure that all the faces are part of our 3D space, we need to apply transform-style: preserve-3d
to the box.
So let’s start writing our styles by initiating some color variables:
$light-gray: #e0e0e0;
$magenta: #ec0071;
$white: #f5f5f5;
.perspective {
font-size: 5em; // sets the main scale size
perspective: 12em; // sets the perspective
perspective-origin: 50% 50%;
text-align: center;
}
.bar {
display: inline-block;
width: 1em;
height: 1em;
margin-top: 1em;
position: relative;
transform: rotateX(60deg); // sets the view point
transform-style: preserve-3d; // perspective for the children
}
Now, let’s think about the faces. If we want to be able to rescale our main box without problems, the faces contained inside must have a liquid behavior and an absolute position.
.bar {
// -> The SCSS written before
.bar-face {
display: inline-block;
width: 100%;
height: 100%;
position: absolute;
bottom: 0;
left: 0;
background-color: rgba($light-gray, .6); // just to see what is happening
}
}
Let’s write the markup and make sure it’s accessible:
<div class="perspective">
<div class="bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="bar-face"></div>
<div class="bar-face"></div>
<div class="bar-face"></div>
<div class="bar-face"></div>
<div class="bar-face"></div>
<div class="bar-face"></div>
</div>
</div>
Setting Up the Faces
This is a very important part. Our bar’s faces must be well oriented, so that we don’t get into trouble when we start adding the percentage fills.
.bar {
// -> The SCSS from before
.bar-face {
// -> The SCSS from before
transform-origin: 50% 100%;
&.roof {
transform: translateZ(1em);
}
&.front {
transform: rotateX(-90deg);
}
&.right {
left: auto;
right: -.5em;
width: 1em;
transform: rotateX(-90deg) rotateY(90deg) translateX(.5em);
}
&.back {
transform: rotateX(-90deg) rotateY(0deg) translateZ(-1em);
}
&.left {
width: 1em;
transform: rotateX(-90deg)rotateY(-90deg) translateX(-.5em) translateZ(.5em);
}
}
}
<div class="perspective">
<div class="bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="bar-face roof"></div>
<div class="bar-face front"></div>
<div class="bar-face left"></div>
<div class="bar-face right"></div>
<div class="bar-face back"></div>
<div class="bar-face floor"></div>
</div>
</div>
Okay, this is a nice cube, but we want to build a rectangle for our bar. If you remember, we’ve already built the faces in a way to be liquid, so if we just increase the width
of our .bar
class we’ll get our shape. For this example we have used a width of 4em.
Building the Percentage Fills
The percentage fills will be contained inside our faces and, to keep our HTML code minimal, we’ll make use of the pseudo class :before
. This generated :before
element will grow to show the percentage relative to its face’s width.
.bar {
// -> The SCSS from before
.bar-face {
// -> The SCSS from before
&.percentage:before {
content: '';
display: block;
position: absolute;
bottom: 0;
width: 0;
height: 100%;
margin: 0;
background-color: rgba($magenta, .8);
transition: width .6s ease-in-out;
}
}
}
<div class="perspective">
<div class="bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="bar-face roof percentage"></div>
<div class="bar-face front percentage"></div>
<div class="bar-face left"></div>
<div class="bar-face right"></div>
<div class="bar-face back percentage"></div>
<div class="bar-face floor percentage"></div>
</div>
</div>
Now we need to write the percentage fill style. It would be a tedious task to write one hundred classes by hand, so let’s write some sassy loop to get all the values for the aria-valuenow
attribute from our HTML.
.bar {
// -> The SCSS from before
.bar-face {
// -> The SCSS from before
}
@for $i from 0 to 101 {
&[aria-valuenow='#{$i}'] {
.percentage:before {
width: $i * 1%;
}
}
}
}
If you want to see it in action, just change the aria-valuenow
attribute in the HTML from 0 to 100.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
Building the Skin
For building our skins, we’ll use Sass mixins. In order to get a realistic look we’ll play with the box-shadow
property. This property supports an array of values, and this array will let us emulate lightings. We’ll include the floor’s shadow and the face’s lighting in this property.
@mixin build-skin($color, $name) {
&.#{$name} {
.floor {
box-shadow:
0 -0.2em 1em rgba(0,0,0,.15),
0 0.2em 0.1em -5px rgba(0,0,0,.3),
0 -0.75em 1.75em rgba($white,.6);
}
.left {
background-color: rgba($color, .5);
}
.percentage:before {
background-color: rgba($color, .5);
box-shadow: 0 1.6em 3em rgba($color,.25);
}
}
}
.bar {
// -> The SCSS from before
@include build-skin(#57caf4, 'cyan');
}
<div class="perspective">
<div class="bar cyan" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
<div class="bar-face roof percentage"></div>
<div class="bar-face front percentage"></div>
<div class="bar-face left"></div>
<div class="bar-face right"></div>
<div class="bar-face back percentage"></div>
<div class="bar-face floor percentage"></div>
</div>
</div>
Also, we need a trick to illuminate our faces. If we type our face’s DOM nodes in the correct order we’ll see the magic happening:
<div class="perspective">
<div class="bar cyan" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
<div class="bar-face roof percentage"></div>
<div class="bar-face back percentage"></div>
<div class="bar-face floor percentage"></div>
<div class="bar-face left"></div>
<div class="bar-face right"></div>
<div class="bar-face front percentage"></div>
</div>
</div>
What has happened here? It’s simple: while the browser is rendering an absolute element it adds an auto-incremented z-index
by default (if we don’t edit this property). So, if we change the rendering order by putting the floor’s face first, its shadow will be over all the faces of the back side. That’s how we can apply a realistic shading.
And that’s all there is to this practical little progress bar! Now, make sure to check out all the demos and the files, and start building your own skins to train your skills.
Thank you for reading, we hope you enjoyed this tutorial and find it useful!
Excellent article. Small suggestion, please include Edge in Browser Support. Edge supports this.
So 3D \m/
Smooth good ^_^
This is the coolest thing I’ve ever seen. well done
Great article, thank you for sharing!
It looks like the link to ‘Introduction to WAI ARIA’ goes to compass website, though.
Link updated 😀
Thanks for the feedback!
looks cack in safari
Hi Callum!
The exercise isn’t prefixed. I use an auto-prefixer to do this, if you’re (like me) using Sublime Text to develop code, you can install the auto-prefixer plug-in through the “Package Control”.
Thanks for your comments guys 😀
2nd time visiting this page, it’s such an excellent idea :3
You should not be bloating your markup with unnecessary divs; there is a
progress
element, and you should use it.I think you’re at the wrong site with that kind of attitude mate
Great stuff!
An interesting use of CSS here to revitalise the tedious process that often involves waiting for something to load. Thanks, Rafael!
I too agree with Vinoth’s opinion to include edge in browser support. Thanks Rafael for sharing this to show the interesting uses of CSS and codes.
this is very cool.
Nice
How to animate them ?
The final result is extremely cool 😎