Joshua Hibbert

Latest Projects

Hire Me

Latest Post

Creating a Pie Timer Only Using CSS

This post will take to read.

View Demo

Creating a pie timer using CSS is a good les­son in cre­at­ive use of pseudo-ele­ments and CSS anim­a­tions and the end res­ult can be used to replace a load­ing spin­ner. Let’s start by cre­atng our pie timer ele­ment (using a single ele­ment to be as semant­ic as pos­sible):

<div class="timer"></div>

Next, let’s get some basic CSS styles sor­ted, includ­ing set­ting the size of our pie timer and ensur­ing that it’s a circle:

.timer {
    border-radius: 50%;
    height: 6em;
    width: 6em;
}

We can’t actu­ally see any­thing yet, so let’s add some col­our. We’re going to use a CSS gradi­ent to fill the left half of our pie timer with our primary col­our and the right half with our sec­ond­ary col­our.

.timer {
    background: linear-gradient(90deg, #6c6 50%, #ddd 50%);
}

Now we need to cre­ate the pseudo-ele­ment that will act as a mask in the pie timer anim­a­tion. Let’s also set the timer element’s positon to rel­at­ive so that we can eas­ily pos­i­tion the mask in rela­tion to the pie timer:

.timer {
    position: relative;
}
.timer:after {
    border-radius: 100% 0 0 100% / 50% 0 0 50%;
    content: '';
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    width: 50%;
}

As the mask will only be used to cov­er half of the pie timer, the width is set to 50% and we’re using border-radius to make the mask a semi­circle. A back­ground col­our has not been set as the mask anim­a­tion will take care of that.

Next we need to build our anim­a­tions. We’ll start with the easi­er of the two; the one that will spin the pie timer around. The key­frames are very straight­for­ward:

@keyframes timer {
    100% { 
        transform: rotate(360deg);
    }
}

We don’t need to set any value at 0% as that’s the pos­i­tion the ele­ment is already in. Now we’re going to use the animation short­hand prop­erty to run our anim­a­tion over 10 seconds with steps 1 second apart. We also want the anim­a­tion looped infin­itely:

.timer {
    animation: timer 10s steps(10, start) infinite;
}

Now it gets a little more com­plex. We need to use the mask to fill the left side of the pie timer ele­ment with the sec­ond­ary col­our dur­ing the first five seconds of the anim­a­tion and then to fill the right side with the primary col­our dur­ing the last five seconds. As our mask is a psuedo-ele­ment, we also need to neg­ate the spin­ning on the pie timer ele­ment to keep our mask on the cor­rect side for each half of the anim­a­tion. Here are our key­frames:

@keyframes mask {
    0% {
        background: #ddd;
        transform: rotate(0deg);
    }
    50% {
        background: #ddd;
        transform: rotate(-180deg);
    }
    50.01% {
        background: #6c6;
        transform: rotate(0deg);
    }
    100% {
        background: #6c6;
        transform: rotate(-180deg);
    }
}

The animation short­hand is basic­ally the same as the first but we only need half the num­ber of steps:

.timer:after {
    animation: mask 10s steps(5, start) infinite;
}

You’ll now notice that the mask is not rotat­ing in per­fect align­ment with the pie timer. This can be fixed by adjust­ing the exact point that it’s rotat­ing around using transform-origin:

.timer:after {
    transform-origin: 100% 50%;
}

And now you have a fully func­tion­al pie timer using a single HTML ele­ment and 43 lines of CSS! How cre­at­ive can you get with it?

Final Product:

<div class="timer"></div>

.timer {
    animation: timer 10s steps(10, start) infinite;
    background: linear-gradient(90deg, #6c6 50%, #ddd 50%);
    border-radius: 50%;
    height: 6em;
    position: relative;
    width: 6em;
}
.timer:after {
    animation: mask 10s steps(5, start) infinite;
    border-radius: 100% 0 0 100% / 50% 0 0 50%;
    content: '';
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    transform-origin: 100% 50%;
    width: 50%;
}
@keyframes timer {
    100% { 
        transform: rotate(360deg);
    }
}
@keyframes mask {
    0% {
        background: #ddd;
        transform: rotate(0deg);
    }
    50% {
        background: #ddd;
        transform: rotate(-180deg);
    }
    50.01% {
        background: #6c6;
        transform: rotate(0deg);
    }
    100% {
        background: #6c6;
        transform: rotate(-180deg);
    }
}