Skip to content

Instantly share code, notes, and snippets.

@hackur
Created July 2, 2025 19:26
Show Gist options
  • Save hackur/61a2e51a74445984d2d5f6815f7c4187 to your computer and use it in GitHub Desktop.
Save hackur/61a2e51a74445984d2d5f6815f7c4187 to your computer and use it in GitHub Desktop.
Animated Process Steps

Animated Process Steps

I wanted to add animation to a css/pug process steps component with minimal javascript. Changing classes fires the animation, the only problem I came across was when going backwards in steps the transition delays started to break up so I had to add a bit more js.

A Pen by Ian Egner on CodePen.

License.

-
var steps = [{
name: 'My First Step',
status: 'done', // done, active or null
},
{
name: 'My Next Step',
status: 'done'
},
{
name: 'Another Step',
status: 'active'
},
{
name: 'My Final Step'
}]
.progress-steps-wrap
- var count = steps.length || 0
.progress-steps(data-count=count)
if steps
each step, i in steps
a.step(href=step.link, class=step.status, data-index=i + 1)= step.name
$(document).on('click', 'a.step', function () {
var $this = $(this)
var $parent = $this.closest('.progress-steps')
// If the bar is moving from right to left, the css transitions
// must be in reverse order, done via parent class
var activeIndex = $parent.find('.active').index()
var thisIndex = $this.index()
if (thisIndex < activeIndex) {
$parent.addClass('reverse')
} else {
$parent.removeClass('reverse')
}
// Remove status classes of all steps
$parent.find('a').removeClass('active done')
// Add done to all steps before, add active to this step
$this.prevAll().addClass('done')
$this.addClass('active')
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
$top-spacing: 12px;
$horizontal-spacing: 72px;
$edge-spacing: $horizontal-spacing / 4;
$line-thickness: 3px;
$default-line-colour: #bfbfbf;
$active-line-colour: #4d7298;
$default-text-colour: #666;
$active-text-colour: #0a0a0a;
$done-text-colour: $default-line-colour;
$icon-size: 24px;
$line-transition: .2s;
.progress-steps-wrap {
width: 100%;
margin-bottom: 18px;
margin-top: 72px;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.progress-steps {
display: inline-block;
margin-right: auto;
margin-left: auto;
&::before {
display: block;
position: relative;
top: $line-thickness;
left: 0;
width: 100%;
height: $line-thickness;
background: $default-line-colour;
content: '';
}
a {
display: inline-block;
position: relative;
padding-top: $top-spacing;
padding-right: $horizontal-spacing / 2;
padding-left: $horizontal-spacing / 2;
color: $default-text-colour;
font-size: .875rem;
font-weight: bold;
letter-spacing: -.0125rem;
cursor: pointer;
&::before {
display: block;
position: relative;
top: 0 - $top-spacing;
left: 0 - ($horizontal-spacing / 2);
width: 0;
height: $line-thickness;
background: $active-line-colour;
content: '';
z-index: 2;
}
&:first-child {
padding-left: $edge-spacing;
&::before {
left: 0 - $edge-spacing;
}
&::after {
left: calc(50% - #{$icon-size});
}
}
&:last-child {
padding-right: $edge-spacing;
&::after {
left: 50%;
}
}
&::after {
display: block;
position: absolute;
top: 0 - (($icon-size / 2 - $top-spacing) + $top-spacing);
left: calc(50% - #{$icon-size / 2});
width: $icon-size;
height: $icon-size * .75;
opacity: 0;
z-index: 3;
padding-top: $icon-size * .125;
padding-bottom: $icon-size * .125;
border-radius: 50%;
background: $active-line-colour;
color: #fefefe;
font-size: .875rem;
font-weight: bold;
line-height: $icon-size * .75;
text-align: center;
content: attr(data-index);
}
&.active {
color: $active-text-colour;
&::before {
width: calc(50% + #{$horizontal-spacing / 2});
}
&::after {
opacity: 1;
}
&:first-child {
&::before {
width: calc(50% + #{$edge-spacing});
}
}
&:last-child {
&::before {
width: calc(50% + #{$horizontal-spacing / 2});
}
}
}
&.done {
color: $done-text-colour;
&::before {
width: calc(100% + #{$horizontal-spacing});
}
&:first-child,
&:last-child {
&::before {
width: calc(100% + #{$horizontal-spacing / 2 + $edge-spacing});
}
}
}
}
a::before {
transition-delay: $line-transition;
transition-duration: $line-transition;
transition-property: width;
transition-timing-function: linear;
}
.active::before {
transition-timing-function: ease-out;
}
@for $i from 1 through 8 {
&[data-count='#{$i}'] {
@for $j from 1 through 8 {
a:nth-child(#{$j})::before,
&.reverse a:nth-child(#{$j})::before {
transition-delay: $line-transition * ($i - $j);
}
}
}
}
@for $i from 1 through 8 {
&[data-count] {
.done:nth-child(#{$i})::before,
.active:nth-child(#{$i})::before {
transition-delay: $line-transition * ($i - 1);
}
}
}
a::after{
transition-delay: $line-transition;
transition-duration: $line-transition / 2;
transition-property: opacity;
transition-timing-function: ease-in;
}
.active::after {
transition-duration: $line-transition;
transition-timing-function: ease-out;
}
@for $i from 1 through 8 {
&[data-count='#{$i}'] {
@for $j from 1 through 8 {
a:nth-child(#{$j})::after,
&.reverse a:nth-child(#{$j})::after {
transition-delay: ($line-transition * ($i - $j)) + $line-transition / 2;
}
}
}
}
@for $i from 1 through 8 {
&[data-count] {
.done:nth-child(#{$i})::after,
.active:nth-child(#{$i})::after {
transition-delay: ($line-transition * ($i - 1)) + $line-transition / 2;
}
}
}
a {
transition-delay: $line-transition;
transition-duration: $line-transition / 2;
transition-property: color;
transition-timing-function: ease-in;
}
.active {
transition-duration: $line-transition;
transition-timing-function: ease-out;
}
@for $i from 1 through 8 {
&[data-count='#{$i}'] {
@for $j from 1 through 8 {
a:nth-child(#{$j}),
&.reverse a:nth-child(#{$j}) {
transition-delay: ($line-transition * ($i - $j)) + $line-transition / 2;
}
}
}
}
@for $i from 1 through 8 {
&[data-count] {
.done:nth-child(#{$i}),
.active:nth-child(#{$i}) {
transition-delay: ($line-transition * ($i - 1)) + $line-transition / 2;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment