Monday 10 September 2012

Pure CSS VS11-like 'working' animation

I thought it would be fun to try my hand at producing a similar effect as the Visual Studio 2012 installer working/busy animation using pure-CSS. I think it turned out pretty well, it works in Opera, Safari, Chrome and Firefox.

It's interesting comparing how it came out in each of the browsers:
  • Safari felt like it was really struggling to handle it,
  • Chrome was drawing the circles a bit choppy due to the floating point positions,
  • Firefox was really nice and smooth, and
  • Opera was good but not quite as good as Firefox.

Demo


Markup

<div id="working-container">
  <div class="working">
    <div class="ball"></div>
    <div class="ball"></div>
    <div class="ball"></div>
    <div class="ball"></div>
    <div class="ball"></div>
  </div>
</div>

The .working needs to be added after the .ball elements are loaded otherwise the timing will be wrong.

CSS

I took out all the vendor-prefixes for simplicity, the SASS below has them included.
#working-container {
  position:relative;
  background-color:#2d2d2d;
  width:300px;
  height:200px;
  margin:20px auto;
}
  
.working {
  position:absolute;
  top:50%;
  left:50%;
}

.working .ball {
  position: absolute;
  background-color: #0D76BD;
  width: 5px;
  height: 5px;
  border-radius: 2.5px;
  opacity: 0;
  animation-name: working;
  animation-duration: 4.6s;
  animation-iteration-count: infinite;
  animation-timing-function: linear; 
}

.working .ball:nth-child(1) {
  animation-delay: 0s; 
}

.working .ball:nth-child(2) {
  animation-delay: 0.4s;
}

.working .ball:nth-child(3) {
  animation-delay: 0.8s; 
}

.working .ball:nth-child(4) {
  -moz-animation-delay: 1.2s; 
}

.working .ball:nth-child(5) {
  animation-delay: 1.6s; 
}

@keyframes working {
  0% {
    left: -85px;
    opacity: 0; 
  }
  3.261% {
    left: -55px; 
  }
  6.522% {
    left: -35px;
    opacity: 1; 
  }
  9.783% {
    left: -20px; 
  }
  32.609% {
    left: 0px; 
  }
  55.435% {
    left: 20px; 
  }
  58.696% {
    left: 35px;
    opacity: 1; 
  }
  61.957% {
    left: 55px; 
  }
  65.217%, 100% {
    left: 85px;
    opacity: 0; 
  } 
}

SASS

$number-of-balls:5;
$ball-delay:0.4;
$duration:3s;
$gap:($number-of-balls - 1) * $ball-delay;
$duration-percentage:$duration / ($duration + $gap);

#working-container {
  position:relative;
  background-color:#2d2d2d;
  width:300px;
  height:200px;
  margin:20px auto;
}
  
.working {
  position:absolute;
  top:50%;
  left:50%;
}
  
.working .ball {
  position:absolute;
  background-color:#0D76BD;
  width:5px;
  height:5px;
  border-radius:2.5px;
  opacity:0;
    
  -moz-animation-name: working;
  -moz-animation-duration: $duration + $gap;
  -moz-animation-iteration-count: infinite;
  -moz-animation-timing-function: linear;
  -o-animation-name: working;
  -o-animation-duration: $duration + $gap;
  -o-animation-iteration-count: infinite;
  -o-animation-timing-function: linear;
  -webkit-animation-name: working;
  -webkit-animation-duration: $duration + $gap;
  -webkit-animation-iteration-count: infinite;
  -webkit-animation-timing-function: linear;
}

@mixin initial-delay($n) {
  $delay:unquote(($n - 1) * $ball-delay + 's');
  -moz-animation-delay:$delay;
  -o-animation-delay:$delay;
  -webkit-animation-delay:$delay;
}
  
.working .ball:nth-child(1) { @include initial-delay(1); }
.working .ball:nth-child(2) { @include initial-delay(2); }
.working .ball:nth-child(3) { @include initial-delay(3); }
.working .ball:nth-child(4) { @include initial-delay(4); }
.working .ball:nth-child(5) { @include initial-delay(5); }
  
@-moz-keyframes working {
  0% {
    left:-85px;
    opacity:0;
  }
  #{5 * $duration-percentage + '%'} {
    left:-55px;
  }
  #{10 * $duration-percentage + '%'} {
    left:-35px;
    opacity:1;
  }
  #{15 * $duration-percentage + '%'} {
    left:-20px;
  }
  #{50 * $duration-percentage + '%'} {
    left:0px;
  }
  #{85 * $duration-percentage + '%'} {
    left:20px;
  }
  #{90 * $duration-percentage + '%'} {
    left:35px;
    opacity:1;
  }
  #{95 * $duration-percentage + '%'} {
    left:55px;
  }
  #{100 * $duration-percentage + '%'}, 100% {
    left:85px;
    opacity:0;
  }
}
  
@-o-keyframes working {
  0% {
    left:-85px;
    opacity:0;
  }
  #{5 * $duration-percentage + '%'} {
    left:-55px;
  }
  #{10 * $duration-percentage + '%'} {
    left:-35px;
    opacity:1;
  }
  #{15 * $duration-percentage + '%'} {
    left:-20px;
  }
  #{50 * $duration-percentage + '%'} {
    left:0px;
  }
  #{85 * $duration-percentage + '%'} {
    left:20px;
  }
  #{90 * $duration-percentage + '%'} {
    left:35px;
    opacity:1;
  }
  #{95 * $duration-percentage + '%'} {
    left:55px;
  }
  #{100 * $duration-percentage + '%'}, 100% {
    left:85px;
    opacity:0;
  }
}
  
@-webkit-keyframes working {
  0% {
    left:-85px;
    opacity:0;
  }
  #{5 * $duration-percentage + '%'} {
    left:-55px;
  }
  #{10 * $duration-percentage + '%'} {
    left:-35px;
    opacity:1;
  }
  #{15 * $duration-percentage + '%'} {
    left:-20px;
  }
  #{50 * $duration-percentage + '%'} {
    left:0px;
  }
  #{85 * $duration-percentage + '%'} {
    left:20px;
  }
  #{90 * $duration-percentage + '%'} {
    left:35px;
    opacity:1;
  }
  #{95 * $duration-percentage + '%'} {
    left:55px;
  }
  #{100 * $duration-percentage + '%'}, 100% {
    left:85px;
    opacity:0;
  }
}