How to create a circular progress button
- Transfer
- Tutorial
Lesson on the implementation of the round download button (hereinafter referred to as the progress button) by Colin Garven. We will use the technique of animating SVG lines described by Jake Archibald in order to animate the progress button and show the user the status of “success and fail”.

Today we will show you how to create an elegant progress button. This is a unique submit button concept proposed by Colin Garven. First take a look at this ( Demo ), try to figure out how to implement it, and just enjoy the animation. The idea is this: when you first click, the submit button transforms into a circle that shows the loading animation using its borders (hereinafter referred to as the border). When the animation is finished, the button will return to its original form and will display a mark indicating that the confirmation was successful or not.
There are several ways to implement a button with this effect. Thinking of implementation only through CSS, the hardest part seems to be the circle of progress. There is an implementation technique using the clip property. Anders Ingemann wrote an excellent complete tutorial(on LESS). But we will use a technique based on SVG, CSS transitions and a bit of JS. As for the progress circle, success / fail marks, we will use the SVG line drawing technique described by Jake Archibald.
It is worth noting that SVG animations can be problematic for some browsers. So take this tutorial only as an experimental exercise.
Go!
If you carefully studied Colin's Dribbble shot, you should have noticed that we should take care of several button states. The interesting part is the transition between two such states. First we need to show a simple button with a transparent background and a color border. When you mouse over, the button is filled with the color of the border, and the text turns white.

When you click on the button (in order to, for example, confirm sending the form), the text should disappear smoothly, the width of the button - will decrease to the state of the circle, the border will become narrower, and the animation should begin. We will use the SVG circle for animation, so we need to make sure that the resulting button is the same size and in the same position as the SVG circle that is visible at this moment. Then we draw a circle depicting the loading of confirmation.

When the confirmation is completed and the circle is completely drawn, the button should expand again, draw a check mark in case of successful confirmation. And paint over the button, respectively.

In case of confirmation failure, it is necessary to show the error status.

Let's create our markup with all the necessary elements.
For markup, we need the main container, a button with a span containing text, and three SVGs:
We use Method Draw , because it is easiest to use the online SVG generator to draw a check mark and a cross for the button. The dimensions of all SVGs will be 70x70, since the height of our button is 70px. If we want a circle with a border of 5 units thick, we need to set the correct radius when we draw it in the graphics editor, so that the whole circle with its border has a height of 70 pixels. Notice that in SVG the stroke is drawn symmetrically to the border of the object. For example, a stroke of 2 px thick will increase the circle with a radius of 10 px to the actual thickness and height of 20 + 2 instead of 20 + 4 (border width twice), that is, the formula 2 * r + border. For our case, we know 2 * r + 5 = 70, from here our circle should have a radius of 32.5 pixels. Thus it turns out: />.
Unfortunately, we cannot use only these basic sizes, because the default insertion parameters for each browser are different and we cannot control where the “loading animation will begin”. So we have to convert this form to path and use it. You can easily do this in Method Draw under Object> Convert to Path.
For the cross, we will use four elements that can be drawn from the midpoint, and animate like a tick.
So, we have all the necessary elements. We will think over the procedure and start styling.
First you need to style the container for the button:
We indicate our button colors and fonts. To match the concept, set the correct border and font Montserrat:
We also set transition for all properties that will be animated (background-color, width etc.).
When you hover over the mouse, we change the background color and text color:
Remove all the strokes (outline):
All SVGs should be centered, all pointer-events disabled:
Forms should not have fill colors, since we want to play with only the stroke. We don’t show elements in any button states except the one we need, so let's hide them by making them transparent:
Our boot circle will be created by setting stroke-width to 5 units:
Success / fail indicators will have a thinner stroke and it should be white. Set the stroke-linecap property to round, so they will be more beautiful. We will set a quick change in transparency for them:
Now let's summarize and recall our master plan. We needed to be able to stylize the three added states (in addition to the default) of the button and special elements. We will use the classes “loading”, “success” and “error” to indicate them.
The button will become round and will look exactly like the SVG boot circle when we start the boot process:
Remember that we already set transition when we set styles for the button. The text should quickly disappear when the loading animation begins, ...
... by setting a value of zero for transparency:
When changing states from loading to success / error, we don’t need transition, just leave the text hidden.
When we remove all classes and return the button to its original state. The text needs a little more time to appear, so we will set other values for the duration of the animation and its delay for a normal state:
When the last state is reached, it is necessary to override the transition, since we do not need to animate the color of the border or the width of the button:
Set the colors for the last state:
When the necessary class is applied, we need to show our SVG and animate the stroke-dashoffset by setting the following value for transition:
Add easing to animate the latitude of the button:
If you like playing with other easing features, try Ceaser , the CSS Easing Animation Tool by Matthew Lein.
We have decided on the styles, we do magic further.
Let's start by initializing some elements: button is the html element of button, progressEl is the SVG element of the progress bar ring, and successEl, errorEl are SVG elements of the checkmark and cross, respectively:
We added the SVGEI function, which will be used to provide SVG elements and their paths. We cache path and accordingly length for each. Initially, we “pull” all paths by controlling the values of the strokeDasharray and strokeDashoffset properties. Later we will “pull” them back when we show the boot circle, or a check mark, or a cross. This technique is well explained by Jake Archibald in the article Animated line drawing in SVG . Set the stroke-dasharray equal to the length of the path and drag it out. By setting stroke-dashoffset also to its length, we no longer see it. When we need to show the figure - set the offset to 0, simulating the drawing of a figure:
Next, we must initialize the onclick event for our button. First, the button will turn into a circle (by adding the loading class). After the end of the animation, either a callback function will be called, or the progress will be set to 100%. At the moment, the button is disabled (this event should be the very first, however, a browser such as firefox, for example, will not be able to remove the transitionend event):
Once progress reaches 100%, you need to update the filling of the boot circle. Then you need to show either a check mark or a cross. After a while (options.statusTime) we will remove all status indicators and turn on the button again. Please note that all transitions are controlled through CSS.
The button is ready!
"We hope you enjoyed this tutorial and find it useful!"

Today we will show you how to create an elegant progress button. This is a unique submit button concept proposed by Colin Garven. First take a look at this ( Demo ), try to figure out how to implement it, and just enjoy the animation. The idea is this: when you first click, the submit button transforms into a circle that shows the loading animation using its borders (hereinafter referred to as the border). When the animation is finished, the button will return to its original form and will display a mark indicating that the confirmation was successful or not.
There are several ways to implement a button with this effect. Thinking of implementation only through CSS, the hardest part seems to be the circle of progress. There is an implementation technique using the clip property. Anders Ingemann wrote an excellent complete tutorial(on LESS). But we will use a technique based on SVG, CSS transitions and a bit of JS. As for the progress circle, success / fail marks, we will use the SVG line drawing technique described by Jake Archibald.
It is worth noting that SVG animations can be problematic for some browsers. So take this tutorial only as an experimental exercise.
Go!
The master plan
If you carefully studied Colin's Dribbble shot, you should have noticed that we should take care of several button states. The interesting part is the transition between two such states. First we need to show a simple button with a transparent background and a color border. When you mouse over, the button is filled with the color of the border, and the text turns white.

When you click on the button (in order to, for example, confirm sending the form), the text should disappear smoothly, the width of the button - will decrease to the state of the circle, the border will become narrower, and the animation should begin. We will use the SVG circle for animation, so we need to make sure that the resulting button is the same size and in the same position as the SVG circle that is visible at this moment. Then we draw a circle depicting the loading of confirmation.

When the confirmation is completed and the circle is completely drawn, the button should expand again, draw a check mark in case of successful confirmation. And paint over the button, respectively.

In case of confirmation failure, it is necessary to show the error status.

Let's create our markup with all the necessary elements.
Markup
For markup, we need the main container, a button with a span containing text, and three SVGs:
<!-- progress button --><divid="progress-button"class="progress-button"><!-- button with text --><button><span>Submit</span></button><!-- svg circle for progress indication --><svgclass="progress-circle"width="70"height="70"><pathd="m35,2.5c17.955803,0 32.5,14.544199 32.5,32.5c0,17.955803 -14.544197,32.5 -32.5,32.5c-17.955803,0 -32.5,-14.544197 -32.5,-32.5c0,-17.955801 14.544197,-32.5 32.5,-32.5z"/></svg><!-- checkmark to show on success --><svgclass="checkmark"width="70"height="70"><pathd="m31.5,46.5l15.3,-23.2"/><pathd="m31.5,46.5l-8.5,-7.1"/></svg><!-- cross to show on error --><svgclass="cross"width="70"height="70"><pathd="m35,35l-9.3,-9.3"/><pathd="m35,35l9.3,9.3"/><pathd="m35,35l-9.3,9.3"/><pathd="m35,35l9.3,-9.3"/></svg></div><!-- /progress-button -->
We use Method Draw , because it is easiest to use the online SVG generator to draw a check mark and a cross for the button. The dimensions of all SVGs will be 70x70, since the height of our button is 70px. If we want a circle with a border of 5 units thick, we need to set the correct radius when we draw it in the graphics editor, so that the whole circle with its border has a height of 70 pixels. Notice that in SVG the stroke is drawn symmetrically to the border of the object. For example, a stroke of 2 px thick will increase the circle with a radius of 10 px to the actual thickness and height of 20 + 2 instead of 20 + 4 (border width twice), that is, the formula 2 * r + border. For our case, we know 2 * r + 5 = 70, from here our circle should have a radius of 32.5 pixels. Thus it turns out: />.
Unfortunately, we cannot use only these basic sizes, because the default insertion parameters for each browser are different and we cannot control where the “loading animation will begin”. So we have to convert this form to path and use it. You can easily do this in Method Draw under Object> Convert to Path.
For the cross, we will use four elements that can be drawn from the midpoint, and animate like a tick.
So, we have all the necessary elements. We will think over the procedure and start styling.
CSS
First you need to style the container for the button:
.progress-button {
position: relative;
display: inline-block;
text-align: center;
}
We indicate our button colors and fonts. To match the concept, set the correct border and font Montserrat:
.progress-buttonbutton {
display: block;
margin: 0 auto;
padding: 0;
width: 250px;
height: 70px;
border: 2px solid #1ECD97;
border-radius: 40px;
background: transparent;
color: #1ECD97;
letter-spacing: 1px;
font-size: 18px;
font-family: 'Montserrat', sans-serif;
-webkit-transition: background-color 0.3s, color 0.3s, width 0.3s, border-width 0.3s, border-color 0.3s;
transition: background-color 0.3s, color 0.3s, width 0.3s, border-width 0.3s, border-color 0.3s;
}
We also set transition for all properties that will be animated (background-color, width etc.).
When you hover over the mouse, we change the background color and text color:
.progress-buttonbutton:hover {
background-color: #1ECD97;
color: #fff;
}
Remove all the strokes (outline):
.progress-buttonbutton:focus {
outline: none;
}
All SVGs should be centered, all pointer-events disabled:
.progress-buttonsvg {
position: absolute;
top: 0;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
pointer-events: none;
}
Forms should not have fill colors, since we want to play with only the stroke. We don’t show elements in any button states except the one we need, so let's hide them by making them transparent:
.progress-buttonsvgpath {
opacity: 0;
fill: none;
}
Our boot circle will be created by setting stroke-width to 5 units:
.progress-buttonsvg.progress-circlepath {
stroke: #1ECD97;
stroke-width: 5;
}
Success / fail indicators will have a thinner stroke and it should be white. Set the stroke-linecap property to round, so they will be more beautiful. We will set a quick change in transparency for them:
.progress-buttonsvg.checkmarkpath,
.progress-buttonsvg.crosspath {
stroke: #fff;
stroke-linecap: round;
stroke-width: 4;
-webkit-transition: opacity 0.1s;
transition: opacity 0.1s;
}
Now let's summarize and recall our master plan. We needed to be able to stylize the three added states (in addition to the default) of the button and special elements. We will use the classes “loading”, “success” and “error” to indicate them.
The button will become round and will look exactly like the SVG boot circle when we start the boot process:
.loading.progress-buttonbutton {
width: 70px; /* make a circle */border-width: 5px;
border-color: #ddd;
background-color: transparent;
color: #fff;
}
Remember that we already set transition when we set styles for the button. The text should quickly disappear when the loading animation begins, ...
.loading.progress-buttonspan {
-webkit-transition: opacity 0.15s;
transition: opacity 0.15s;
}
... by setting a value of zero for transparency:
.loading.progress-buttonspan,
.success.progress-buttonspan,
.error.progress-buttonspan {
opacity: 0; /* keep it hidden in all states */
}
When changing states from loading to success / error, we don’t need transition, just leave the text hidden.
When we remove all classes and return the button to its original state. The text needs a little more time to appear, so we will set other values for the duration of the animation and its delay for a normal state:
/* Transition for when returning to default state */.progress-buttonbuttonspan {
-webkit-transition: opacity 0.3s0.1s;
transition: opacity 0.3s0.1s;
}
When the last state is reached, it is necessary to override the transition, since we do not need to animate the color of the border or the width of the button:
.success.progress-buttonbutton,
.error.progress-buttonbutton {
-webkit-transition: background-color 0.3s, width 0.3s, border-width 0.3s;
transition: background-color 0.3s, width 0.3s, border-width 0.3s;
}
Set the colors for the last state:
.success.progress-buttonbutton {
border-color: #1ECD97;
background-color: #1ECD97;
}
.error.progress-buttonbutton {
border-color: #FB797E;
background-color: #FB797E;
}
When the necessary class is applied, we need to show our SVG and animate the stroke-dashoffset by setting the following value for transition:
.loading.progress-buttonsvg.progress-circlepath,
.success.progress-buttonsvg.checkmarkpath,
.error.progress-buttonsvg.crosspath {
opacity: 1;
-webkit-transition: stroke-dashoffset 0.3s;
transition: stroke-dashoffset 0.3s;
}
Add easing to animate the latitude of the button:
.elastic.progress-buttonbutton {
-webkit-transition: background-color 0.3s, color 0.3s, width 0.3scubic-bezier(0.25, 0.25, 0.4, 1), border-width 0.3s, border-color 0.3s;
-webkit-transition: background-color 0.3s, color 0.3s, width 0.3scubic-bezier(0.25, 0.25, 0.4, 1.6), border-width 0.3s, border-color 0.3s;
transition: background-color 0.3s, color 0.3s, width 0.3scubic-bezier(0.25, 0.25, 0.4, 1.6), border-width 0.3s, border-color 0.3s;
}
.loading.elastic.progress-buttonbutton {
-webkit-transition: background-color 0.3s, color 0.3s, width 0.3scubic-bezier(0.6, 0, 0.75, 0.75), border-width 0.3s, border-color 0.3s;
-webkit-transition: background-color 0.3s, color 0.3s, width 0.3scubic-bezier(0.6, -0.6, 0.75, 0.75), border-width 0.3s, border-color 0.3s;
transition: background-color 0.3s, color 0.3s, width 0.3scubic-bezier(0.6, -0.6, 0.75, 0.75), border-width 0.3s, border-color 0.3s;
}
If you like playing with other easing features, try Ceaser , the CSS Easing Animation Tool by Matthew Lein.
We have decided on the styles, we do magic further.
Javascript
Let's start by initializing some elements: button is the html element of button, progressEl is the SVG element of the progress bar ring, and successEl, errorEl are SVG elements of the checkmark and cross, respectively:
functionUIProgressButton( el, options ) {
this.el = el;
this.options = extend( {}, this.options );
extend( this.options, options );
this._init();
}
UIProgressButton.prototype._init = function() {
this.button = this.el.querySelector( 'button' );
this.progressEl = new SVGEl( this.el.querySelector( 'svg.progress-circle' ) );
this.successEl = new SVGEl( this.el.querySelector( 'svg.checkmark' ) );
this.errorEl = new SVGEl( this.el.querySelector( 'svg.cross' ) );
// init eventsthis._initEvents();
// enable buttonthis._enable();
}
We added the SVGEI function, which will be used to provide SVG elements and their paths. We cache path and accordingly length for each. Initially, we “pull” all paths by controlling the values of the strokeDasharray and strokeDashoffset properties. Later we will “pull” them back when we show the boot circle, or a check mark, or a cross. This technique is well explained by Jake Archibald in the article Animated line drawing in SVG . Set the stroke-dasharray equal to the length of the path and drag it out. By setting stroke-dashoffset also to its length, we no longer see it. When we need to show the figure - set the offset to 0, simulating the drawing of a figure:
functionSVGEl( el ) {
this.el = el;
// the path elementsthis.paths = [].slice.call( this.el.querySelectorAll( 'path' ) );
// we will save both paths and its lengths in arraysthis.pathsArr = newArray();
this.lengthsArr = newArray();
this._init();
}
SVGEl.prototype._init = function() {
var self = this;
this.paths.forEach( function( path, i ) {
self.pathsArr[i] = path;
path.style.strokeDasharray = self.lengthsArr[i] = path.getTotalLength();
} );
// undraw strokethis.draw(0);
}
// val in [0,1] : 0 - no stroke is visible, 1 - stroke is visible
SVGEl.prototype.draw = function( val ) {
for( var i = 0, len = this.pathsArr.length; i < len; ++i ){
this.pathsArr[ i ].style.strokeDashoffset = this.lengthsArr[ i ] * ( 1 - val );
}
}
Next, we must initialize the onclick event for our button. First, the button will turn into a circle (by adding the loading class). After the end of the animation, either a callback function will be called, or the progress will be set to 100%. At the moment, the button is disabled (this event should be the very first, however, a browser such as firefox, for example, will not be able to remove the transitionend event):
UIProgressButton.prototype._initEvents = function() {
var self = this;
this.button.addEventListener( 'click', function() { self._submit(); } );
}
UIProgressButton.prototype._submit = function() {
classie.addClass( this.el, 'loading' );
var self = this,
onEndBtnTransitionFn = function( ev ) {
if( support.transitions ) {
this.removeEventListener( transEndEventName, onEndBtnTransitionFn );
}
this.setAttribute( 'disabled', '' );
if( typeof self.options.callback === 'function' ) {
self.options.callback( self );
}
else {
self.setProgress(1);
self.stop();
}
};
if( support.transitions ) {
this.button.addEventListener( transEndEventName, onEndBtnTransitionFn );
}
else {
onEndBtnTransitionFn();
}
}
Once progress reaches 100%, you need to update the filling of the boot circle. Then you need to show either a check mark or a cross. After a while (options.statusTime) we will remove all status indicators and turn on the button again. Please note that all transitions are controlled through CSS.
UIProgressButton.prototype.stop = function( status ) {
var self = this,
endLoading = function() {
self.progressEl.draw(0);
if( typeof status === 'number' ) {
var statusClass = status >= 0 ? 'success' : 'error',
statusEl = status >=0 ? self.successEl : self.errorEl;
statusEl.draw( 1 );
// add respective class to the element
classie.addClass( self.el, statusClass );
// after options.statusTime remove status and undraw the respective stroke and enable the button
setTimeout( function() {
classie.remove( self.el, statusClass );
statusEl.draw(0);
self._enable();
}, self.options.statusTime );
}
else {
self._enable();
}
classie.removeClass( self.el, 'loading' );
};
// give it a little time (ideally the same like the transition time) so that the last progress increment animation is still visible.
setTimeout( endLoading, 300 );
}
The button is ready!
"We hope you enjoyed this tutorial and find it useful!"