How to style an HTML5 Progress Element as Circle/Pie with pure CSS

Issue

HTML5 introduced a new “progress” element that by default is rendered as a progress bar (thermometer).

A very basic example is:

<progress max="100" value="85"></progress>

I have been experimenting with a variety of progress circle options using javascript, and also have been really impressed by some pure CSS approaches discussed here:
CSS Progress Circle

I am interested to know if anyone has successfully applied CSS to the “progress” element to provide a pie/clock/circle rendering rather than a linear display?

EDIT/ADDENDUM: The “meter” element is also quite similar to “progress” but provides for a low/high range…I mention this more for anyone who might stumble upon this post in the future and want to apply a similar technique to the HTML5 meter element.

Solution

Trying to do this in pure CSS is quite hard, so I don’t think than this is the correct technique to do it.

Anyway, just as a technical exercise, let’s try it. (Tested only in Chrome !)

First of all the basis. We are going to divide the circle in 4 quadrants, and for each one we will need a different style. Here we have the styles, showing in color (green, red, blue, yellow) the useful range of the progress value element. The gray area is the rest of the element, unused.

.test {
  width: 100px;
  height: 100px;
  margin: 20px 10px 0px 20px;
  border-radius: 50%;
  background-image: radial-gradient(lightblue 62%, blue 40%);
  position: relative;
  display: inline-block;
}

.test div {
	height: 30%;
	transform-origin: left top;
    position: absolute;
    opacity: 0.5;
	ackground-color: green;
}

.inner1 {
	width: 25%;
	left: 50%;
    top: -20%;
	background-color: green;
	transform: rotate(45deg) scaleX(3.9598);
}

.inner2 {
	width: 50%;
	left: 190%;
    top: -20%;
	background-image: linear-gradient(to right,gray 50%, red 50%);
	transform: rotate(135deg) scaleX(3.9598);
}

.inner3 {
	width: 75%;
	left: 190%;
    top: 260%;
	background-image: linear-gradient(to right,gray 66%, blue 66%);
	transform: rotate(225deg) scaleX(3.9598);
}

.inner4 {
	width: 100%;
	left: -230%;
    top: 260%;
	background-image: linear-gradient(to right,gray 75%, yellow 66%);
	transform: rotate(315deg) scaleX(3.9598);
}
<div class="test">
    <div class="inner1"></div>
</div>
<div class="test">
    <div class="inner2"></div>
</div>
<div class="test">
    <div class="inner3"></div>
</div>
<div class="test">
    <div class="inner4"></div>
</div>

Now, let’s show a trick for creating the radial segments. This can be acomplished setting an element normal (at a right angle) to the user, and applying some perspective:

div {
	width: 300px;
	height: 300px;
	position: relative;
}

.container {
	perspective: 400px;
	margin: 40px 200px;
	border: solid 1px black;
}

.top {
    position: absolute;
    left: 0px;
    top: -100%;
    background-image: repeating-linear-gradient(to right, tomato 0px, white 20px);
    transform: rotateX(90deg);
    transform-origin: center bottom;	
}

.right {
    position: absolute;
    left: 100%;
    top: 0px;
    background-image: repeating-linear-gradient( tomato 0px, white 20px);
    transform: rotateY(90deg);
    transform-origin: left center;	
}

.bottom {
    position: absolute;
    left: 0px;
    bottom: 0px;
    background-image: repeating-linear-gradient(to right, tomato 0px, white 20px);
    transform: rotateX(90deg);
    transform-origin: center bottom;	
}

.left {
    position: absolute;
    right: 100%;
    top: 0px;
    background-image: repeating-linear-gradient( tomato 0px, white 20px);
    transform: rotateY(-90deg);
    transform-origin: right center;	
}
<div class="container">
<div class="top"></div>
<div class="right"></div>
<div class="bottom"></div>
<div class="left"></div>
</div>

And now, just some boring selectors (it’s difficult to target values in the range 20-29 and not targetting the value 2 at the same time).

A little bit of JS, but only to control the progress value. You can use both the input and the slider to change it.

function change () {
    var input = document.getElementById("input");
    var progress = document.getElementById("test");
    progress.value = input.value;
}

function changeNumber () {
    var input = document.getElementById("number");
    var progress = document.getElementById("test");
    progress.value = input.value;
}
.container {
	width: 500px;
	height: 500px;
	overflow: hidden;
	margin: 10px;
}
.test {
  width: 200px;
  height: 200px;
  margin: 10px 10px;
  border-radius: 50%;
  background-image: radial-gradient(lightblue 62%, transparent 40%);
  box-shadow: 0px 0px 0px 500px lightblue, inset 0px 0px 0px 2px lightblue;
}



.test::-webkit-progress-bar {
	background-color: transparent;
	position: relative;
    border-radius: 50%;
    perspective: 100px;
    z-index: -1;
	background-repeat: no-repeat;
}

.test[value^="2"]::-webkit-progress-bar,
.test[value^="3"]::-webkit-progress-bar 
{
	background-image: linear-gradient(red, red);
	background-size: 50% 50%;
	background-position: right top;
}

.test[value^="4"]::-webkit-progress-bar,
.test[value^="5"]::-webkit-progress-bar 
{
	background-image: linear-gradient(purple, purple);
	background-size: 50% 100%;
	background-position: right top;
}

.test[value^="6"]::-webkit-progress-bar,
.test[value^="7"]::-webkit-progress-bar,
.test[value="80"]::-webkit-progress-bar 
{
	background-image: linear-gradient(blue, blue), linear-gradient(blue, blue);
	background-size: 50% 100%, 50% 50%;
	background-position: right top, left bottom;
}



.test::-webkit-progress-bar, 
.test[value="2"]::-webkit-progress-bar, 
.test[value="3"]::-webkit-progress-bar, 
.test[value="4"]::-webkit-progress-bar, 
.test[value="5"]::-webkit-progress-bar, 
.test[value="6"]::-webkit-progress-bar, 
.test[value="7"]::-webkit-progress-bar, 
.test[value="8"]::-webkit-progress-bar {
	background-image: none;
	
} 

.test::-webkit-progress-value {
	background-color: green;
	height: 30%;
	transform-origin: left top;
	z-index: -1;
    position: absolute;
}

.test[value^="2"]::-webkit-progress-value,
.test[value^="3"]::-webkit-progress-value {
	background-color: red;
    top: -20%;
    left: 190%;
    transform: rotate(135deg) rotateX(-90deg) scaleX(3.9598);
}



.test[value^="4"]::-webkit-progress-value,
.test[value^="5"]::-webkit-progress-value {
	background-color: purple;
    left: 190%;
    top: 260%;
    transform: rotate(225deg) rotateX(-90deg) scaleX(3.9598);
}

.test[value^="6"]::-webkit-progress-value,
.test[value^="7"]::-webkit-progress-value,
.test[value="80"]::-webkit-progress-value {
	background-color: blue;
    left: -230%;
    top: 260%;
    transform: rotate(315deg) rotateX(-90deg) scaleX(3.9598);
}

.test::-webkit-progress-value, 
.test[value="2"]::-webkit-progress-value, 
.test[value="3"]::-webkit-progress-value, 
.test[value="4"]::-webkit-progress-value, 
.test[value="5"]::-webkit-progress-value, 
.test[value="6"]::-webkit-progress-value, 
.test[value="7"]::-webkit-progress-value, 
.test[value="8"]::-webkit-progress-value 
{
	background-color: green;
     left: 50%;
     top: -20%;
     transform: rotate(45deg) rotateX(-90deg) scaleX(3.9598);
}
<input id="input" type="range" value="0" min="0" max="80" onchange="change()" oninput="change()"/>
<input id="number" type="number" value="0" min="0" max="80" step="1" oninput="changeNumber()"/>
<div class="container">
<progress class="test" id="test" max="80" value="0"></progress>
</div>

There is a difficulty in the overflow: hidden; and a bug in Chrome. It isn’t expected for it to work on the same element where perspective is applied, but it should work applied to the progress itself. It just works half of the time …

Also, another idea, the style is much more simpler, and I could get it to extend to the full range, but anyway it’s a starting point:

function change () {
    var input = document.getElementById("input");
    var progress = document.getElementById("test");
    progress.value = input.value;
}

function changeNumber () {
    var input = document.getElementById("number");
    var progress = document.getElementById("test");
    progress.value = input.value;
}
.test {
  width: 400px;
  height: 200px;
  margin: 10px 10px;
  border-radius: 9999px 9999px 0px 0px;
  border: solid 1px red;
  ackground-image: radial-gradient(lightblue 62%, transparent 40%);
  ox-shadow: 0px 0px 0px 500px lightblue;
  overflow: hidden;
}



.test::-webkit-progress-bar {
	background-color: transparent;
	position: relative;
    border-radius: 50%;
    perspective: 100px;
    perspective-origin: center 300px;
    z-index: -1;
	background-repeat: no-repeat;
}


.test::-webkit-progress-value {
	height: 300%;
	transform-origin: center bottom;
	bottom: -20%;
	z-index: -1;
    position: absolute;
	background-image: linear-gradient(270deg, red 2px, tomato 30px);
    transform:  rotateX(-90deg) scaleX(1);
}
<input id="input" type="range" value="0" min="0" max="80" onchange="change()" oninput="change()">
<input id="number" type="number" value="0" min="0" max="80" step="1" oninput="changeNumber()">
<progress class="test" id="test" max="80" value="20"></progress>

Answered By – vals

Answer Checked By – Gilberto Lyons (AngularFixing Admin)

Leave a Reply

Your email address will not be published.