<!DOCTYPE html>
<html>
<head>
</head>
<body>

<!-- Button container -->
<div class="download-button-container">
    <!-- The real button -->
    <button class="download-button">
        <span class="button-text-real hidden">Download</span>
        <!-- Extra elements to perform the animations -->
        <span class="button-icon">
            <span class="button-linear-progress">
                <span class="button-linear-progress-bar"></span>
            </span>
            <svg class="button-icon-svg" viewBox="0 0 60 60">
                <path class="button-icon-path button-icon-path-square" d="M 20 40 l 0 -20 l 20 0 l 0 20 Z"></path>
                <path class="button-icon-path button-icon-path-line" d="M 40 20 l -20 20"></path>
            </svg>
        </span>
    </button>
    <!-- Extra elements to perform the animations -->
    <svg class="border-svg" width="240px" height="100px" viewBox="0 0 240 100">
        <path class="border-path hidden" d="M 40 3.5 a 36.5 36.5 0 0 0 -36.5 36.5 a 36.5 36.5 0 0 0 36.5 36.5 C 70 76.5 90 76.5 120 76.5 S 170 76.5 200 76.5 a 36.5 36.5 0 0 0 36.5 -36.5 a 36.5 36.5 0 0 0 -36.5 -36.5 Z"></path>
    </svg>
    <span class="button-text button-text-download">Download</span>
    <span class="button-text button-text-done">Done!</span>
    <div class="button-wave"></div>
    <div class="button-progress-container">
        <svg class="button-svg">
            <path class="button-circular-progress" d="M 50 50 m 0 -32.5 a 32.5 32.5 0 0 1 0 65 a 32.5 32.5 0 0 1 0 -65"></path>
        </svg>
        <span class="button-ball"></span>
    </div>
</div>


</body>
</html>
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */

//
// 1. Set default font family to sans-serif.
// 2. Prevent iOS and IE text size adjust after device orientation change,
//    without disabling user zoom.
//

html {
  font-family: sans-serif; // 1
  -ms-text-size-adjust: 100%; // 2
  -webkit-text-size-adjust: 100%; // 2
}

//
// Remove default margin.
//

body {
  margin: 0;
}

// HTML5 display definitions
// ==========================================================================

//
// Correct `block` display not defined for any HTML5 element in IE 8/9.
// Correct `block` display not defined for `details` or `summary` in IE 10/11
// and Firefox.
// Correct `block` display not defined for `main` in IE 11.
//

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
  display: block;
}

//
// 1. Correct `inline-block` display not defined in IE 8/9.
// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
//

audio,
canvas,
progress,
video {
  display: inline-block; // 1
  vertical-align: baseline; // 2
}

//
// Prevent modern browsers from displaying `audio` without controls.
// Remove excess height in iOS 5 devices.
//

audio:not([controls]) {
  display: none;
  height: 0;
}

//
// Address `[hidden]` styling not present in IE 8/9/10.
// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
//

[hidden],
template {
  display: none;
}

// Links
// ==========================================================================

//
// Remove the gray background color from active links in IE 10.
//

a {
  background-color: transparent;
}

//
// Improve readability of focused elements when they are also in an
// active/hover state.
//

a:active,
a:hover {
  outline: 0;
}

// Text-level semantics
// ==========================================================================

//
// Address styling not present in IE 8/9/10/11, Safari, and Chrome.
//

abbr[title] {
  border-bottom: 1px dotted;
}

//
// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
//

b,
strong {
  font-weight: bold;
}

//
// Address styling not present in Safari and Chrome.
//

dfn {
  font-style: italic;
}

//
// Address variable `h1` font-size and margin within `section` and `article`
// contexts in Firefox 4+, Safari, and Chrome.
//

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

//
// Address styling not present in IE 8/9.
//

mark {
  background: #ff0;
  color: #000;
}

//
// Address inconsistent and variable font size in all browsers.
//

small {
  font-size: 80%;
}

//
// Prevent `sub` and `sup` affecting `line-height` in all browsers.
//

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sup {
  top: -0.5em;
}

sub {
  bottom: -0.25em;
}

// Embedded content
// ==========================================================================

//
// Remove border when inside `a` element in IE 8/9/10.
//

img {
  border: 0;
}

//
// Correct overflow not hidden in IE 9/10/11.
//

svg:not(:root) {
  overflow: hidden;
}

// Grouping content
// ==========================================================================

//
// Address margin not present in IE 8/9 and Safari.
//

figure {
  margin: 1em 40px;
}

//
// Address differences between Firefox and other browsers.
//

hr {
  box-sizing: content-box;
  height: 0;
}

//
// Contain overflow in all browsers.
//

pre {
  overflow: auto;
}

//
// Address odd `em`-unit font size rendering in all browsers.
//

code,
kbd,
pre,
samp {
  font-family: monospace, monospace;
  font-size: 1em;
}

// Forms
// ==========================================================================

//
// Known limitation: by default, Chrome and Safari on OS X allow very limited
// styling of `select`, unless a `border` property is set.
//

//
// 1. Correct color not being inherited.
//    Known issue: affects color of disabled elements.
// 2. Correct font properties not being inherited.
// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
//

button,
input,
optgroup,
select,
textarea {
  color: inherit; // 1
  font: inherit; // 2
  margin: 0; // 3
}

//
// Address `overflow` set to `hidden` in IE 8/9/10/11.
//

button {
  overflow: visible;
}

//
// Address inconsistent `text-transform` inheritance for `button` and `select`.
// All other form control elements do not inherit `text-transform` values.
// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
// Correct `select` style inheritance in Firefox.
//

button,
select {
  text-transform: none;
}

//
// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
//    and `video` controls.
// 2. Correct inability to style clickable `input` types in iOS.
// 3. Improve usability and consistency of cursor style between image-type
//    `input` and others.
//

button,
html input[type="button"], // 1
input[type="reset"],
input[type="submit"] {
  -webkit-appearance: button; // 2
  cursor: pointer; // 3
}

//
// Re-set default cursor for disabled elements.
//

button[disabled],
html input[disabled] {
  cursor: default;
}

//
// Remove inner padding and border in Firefox 4+.
//

button::-moz-focus-inner,
input::-moz-focus-inner {
  border: 0;
  padding: 0;
}

//
// Address Firefox 4+ setting `line-height` on `input` using `!important` in
// the UA stylesheet.
//

input {
  line-height: normal;
}

//
// It's recommended that you don't attempt to style these elements.
// Firefox's implementation doesn't respect box-sizing, padding, or width.
//
// 1. Address box sizing set to `content-box` in IE 8/9/10.
// 2. Remove excess padding in IE 8/9/10.
//

input[type="checkbox"],
input[type="radio"] {
  box-sizing: border-box; // 1
  padding: 0; // 2
}

//
// Fix the cursor style for Chrome's increment/decrement buttons. For certain
// `font-size` values of the `input`, it causes the cursor style of the
// decrement button to change from `default` to `text`.
//

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

//
// 1. Address `appearance` set to `searchfield` in Safari and Chrome.
// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
//

input[type="search"] {
  -webkit-appearance: textfield; // 1
  box-sizing: content-box; //2
}

//
// Remove inner padding and search cancel button in Safari and Chrome on OS X.
// Safari (but not Chrome) clips the cancel button when the search input has
// padding (and `textfield` appearance).
//

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

//
// Define consistent border, margin, and padding.
//

fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

//
// 1. Correct `color` not being inherited in IE 8/9/10/11.
// 2. Remove padding so people aren't caught out if they zero out fieldsets.
//

legend {
  border: 0; // 1
  padding: 0; // 2
}

//
// Remove default vertical scrollbar in IE 8/9/10/11.
//

textarea {
  overflow: auto;
}

//
// Don't inherit the `font-weight` (applied by a rule above).
// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
//

optgroup {
  font-weight: bold;
}

// Tables
// ==========================================================================

//
// Remove most spacing between table cells.
//

table {
  border-collapse: collapse;
  border-spacing: 0;
}

td,
th {
  padding: 0;
}

// Some variables to use later
$button-width: 300px;
$button-height: 70px;
$button-border: 3px;
$icon-padding: 5px;
$icon-width: $button-height - ($icon-padding * 2);
$ball-width: 18px;

body {
  background-color: #323536;
}

// Button container
.download-button-container {
  position: fixed;
  width: $button-width;
  height: $button-height;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  text-align: center;

  // Following are the different states for the button: downloading, progressing and completed
  // We have defined the states in the container to have access to all descendants in CSS

  // Downloading: The download button has been pressed
  &.downloading {

    .download-button {
      width: $button-height;
    }

    .button-text-download {
      top: 80%;
      transform: translateY(-50%) scale(1);
      opacity: 0;
    }

    .button-wave {
      left: -10px;

      &:before {
        background-color: transparent;
        transform: scale(1.6);
      }
    }

    .button-icon-svg {
      transform: rotate(0deg);
    }

    .button-icon-path-square {
      fill: white;
    }
  }

  // Progressing: The progress starts
  &.progressing {

    .button-icon {
      left: 50%;
      transform: translate(-50%, -50%);
    }

    .button-ball {
      left: 43px;
      top: 8px;
      width: $ball-width;
      height: $ball-width;
    }
  }

  // Completed: The progress ends
  &.completed {

    .download-button {
      width: $button-width - $button-height;
    }

    .button-icon {
      left: 50%;
      transform: translate(-50%, -50%);
    }

    .button-icon, .button-svg {
      opacity: 0;
      transition: 0.3s;
    }

    .button-text-done {
      transform: translate(-50%, -50%);
      opacity: 1;
      transition: 0.8s 1s cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */
    }

    .border-path {
      stroke: #00FF8D;
    }

    .button-ball {
      left: 50%;
      transition: none;
      // CSS animations for the ball. All of them starts at the same time, so we need to take care of delays
      animation:
              ball-throw-up 0.5s ease-out forwards, // Throw up the ball for 0.5s
              ball-throw-down 0.5s 0.5s ease-in forwards, // Wait 0.5 seconds (throw up), and throw down the ball for 0.5s
              ball-rubber 1s forwards; // Move the ball like a rubber deformation during 1s (throw up + throw down)
    }
  }
}

// Throw up animation
@keyframes ball-throw-up {
  from {
    transform: translate(-50%, 17.5px);
  }
  to {
    transform: translate(-50%, -60px);
    background-color: #00FF8D;
  }
}

// Throw down animation
@keyframes ball-throw-down {
  from {
    transform: translate(-50%, -60px);
  }
  to {
    transform: translate(-50%, 80px);
  }
}

// Rubber animation
@keyframes ball-rubber {
  from {
    width: $ball-width;
  }
  25% {
    width: $ball-width * 0.75;
  }
  50% {
    width: $ball-width;
  }
  to {
    width: $ball-width / 2;
  }
}

// Real button styles
.download-button {
  position: relative;
  display: inline-block;
  width: $button-width;
  height: $button-height;
  background-color: #2C2E2F;
  border: none;
  box-shadow: 0 0 0 $button-border #02D1FF; // This will be the 'border'
  border-radius: 100px;
  cursor: pointer;
  transition: 1s width, 0.3s box-shadow;

  // Remove the default behavior of buttons
  &, &:focus {
    padding: 0;
    outline: none;
  }
  &::-moz-focus-inner {
    border: 0;
  }

  // Styles for the different states of the button
  &:hover, &:active, &:focus {
    box-shadow: 0 0 0 $button-border #02D1FF, 0 0 20px $button-border darken(#02D1FF, 20%);
  }
  &.button-hidden {
    box-shadow: 0 0 0 $button-border transparent;
    &:hover, &:active, &:focus {
      box-shadow: 0 0 0 $button-border transparent, 0 0 20px $button-border darken(#02D1FF, 20%);
    }
  }
}

.button-icon {
  position: absolute;
  left: 5px;
  top: 50%;
  transform: translateY(-50%);
  width: $icon-width;
  height: $icon-width;
  background-color: #02D1FF;
  border-radius: 100%;
}

.button-icon-svg {
  width: 100%;
  height: 100%;
  transform: rotate(-225deg);
  transition: 1s transform;
}

.button-icon-path {
  fill: transparent;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: $button-border;
  stroke: white;
}

.button-icon-path-square {
  transition: 0.8s 0.2s fill;
}

.button-text {
  position: absolute;
  top: 50%;
  color: white;
  font-size: 1.3em;
  pointer-events: none;
}

.button-text-download {
  left: $button-width / 2 - $icon-width / 2;
  transform: translateY(-50%);
  transition: transform 1s, top 0.3s 0.2s, opacity 0.3s 0.2s;
}

.button-text-done {
  left: 50%;
  transform: translate(-50%, $button-height / 2);
  opacity: 0;
  transition: 0.5s opacity, 1s transform;
}

.button-wave {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  left: -9999em;
  top: 50%;
  transform: translateY(-50%);
  width: $icon-width + 30px;
  height: $icon-width + 30px;
  border-radius: 100%;
  overflow: hidden;
  pointer-events: none;

  &:before {
    content: '';
    width: $icon-width;
    height: $icon-width;
    background-color: rgba(0, 250, 255, 1);
    border: 5px solid rgba(0, 250, 255, 1);
    border-radius: 100%;
    transition: 0.5s background, 1s transform;
    transition-timing-function: ease-out;
  }
}

.button-progress-container {
  position: absolute;
  width: 100px;
  height: 100px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.button-svg {
  width: 100%;
  height: 100%;

  .button-circular-progress {
    fill: none;
    stroke-width: 5px;
    stroke: white;
  }
}

.button-ball {
  position: absolute;
  left: 0px;
  top: 0;
  width: 0;
  height: 0;
  background-color: #8c0c1b;
  border-radius: 100%;
  pointer-events: none;
  transition: 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.875); /* easeOutBackModified */
  transition-property: left, top, width, height;
}

.button-linear-progress {
  position: absolute;
  width: $icon-width - $icon-padding * 2;
  height: $icon-width - $icon-padding * 2;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background-color: #00AFD7;
  border-radius: 100%;
  overflow: hidden;
}

.button-linear-progress-bar {
  display: inline-block;
  width: 100%;
  height: 100%;
  background-color: #02D1FF;
}

.border-svg {
  position: absolute;
  top: -5px;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
}

.border-path {
  fill: #2C2E2F;
  stroke-width: $button-border;
  stroke: #02D1FF;
  transition: 0.3s 1s stroke;
}

// Helper class to hide elements
.hidden {
  visibility: hidden !important;
  opacity: 0 !important;
}

html {
  background:white !important;
}
View Compiled
(function() {

    // Some variables to use later
    var buttonContainer = document.querySelector('.download-button-container');
    var button = buttonContainer.querySelector('.download-button');
    var ball = buttonContainer.querySelector('.button-ball');
    var circularProgress = buttonContainer.querySelector('.button-circular-progress');
    var circularProgressLength = circularProgress.getTotalLength();
    var linearProgress = buttonContainer.querySelector('.button-linear-progress-bar');
    var borderPath = buttonContainer.querySelector('.border-path');
    var iconSquarePath = buttonContainer.querySelector('.button-icon-path-square');
    var iconLinePath = buttonContainer.querySelector('.button-icon-path-line');
    var circularProgressBar = new Segment(circularProgress, 0, 0);
    var iconSquare = new Segment(iconSquarePath, '30%', '70%');
    var iconLine = new Segment(iconLinePath, 0, '100%');
    var downloading = false;
    var completed = false;
    var progressTimer = 0;

    // Capture click events
    button.addEventListener('click', function () {
        if (!completed) { // Don't do anything if downloading has been completed
            if (downloading) { // If it's downloading, stop the download
                stopDownload();
            } else { // Start the download
                startDownload();
            }
        }
    });

    // Start the download
    function startDownload() {
        // Update variables and CSS classes
        downloading = true;
        buttonContainer.classList.add('downloading');
        animateIcon();
        // Update progress after 1s
        progressTimer = setTimeout(function () {
            buttonContainer.classList.add('progressing');
            animateProgress();
        }, 1000);
    }

    // Stop the download
    function stopDownload() {
        // Update variables and CSS classes
        downloading = false;
        clearTimeout(progressTimer);
        buttonContainer.classList.remove('downloading');
        buttonContainer.classList.remove('progressing');
        // Stop progress and draw icons back to initial state
        stopProgress();
        iconLine.draw(0, '100%', 1, {easing: anime.easings['easeOutCubic']});
        iconSquare.draw('30%', '70%', 1, {easing: anime.easings['easeOutQuad']});
    }

    function animateIcon() {
        iconLine.draw(0, 0, 0.5);
        iconSquare.draw(0, '100%', 1);
    }

    function stopProgress() {
        circularProgressBar.stop();
        circularProgressBar.draw(0, 0, 0);
        updateProgress(circularProgressBar, true);
    }

    // Update the circular and linear progress bars
    function updateProgress(instance, keepBallPosition) {
        if (!keepBallPosition) {
            var point = instance.path.getPointAtLength(instance.end);
            ball.style.transform = 'translate(' + point.x + 'px, ' + point.y + 'px)';
        }
        linearProgress.style.transform = 'translateY(-'+ instance.end * 100 / circularProgressLength +'%)';
    }

    // Progress animation
    function animateProgress() {
        // Fake progress animation from 0 to 100%
        // This should be replaced with real progress data (real progress percent instead '100%'), and maybe called multiple times
        circularProgressBar.draw(0, '100%', 2.5, {easing: anime.easings['easeInQuart'], update: updateProgress, callback: completedAnimation});

        // // Another example to see a different fake progress (uncomment this and comment line above)
        // circularProgressBar.draw(0, '40%', 1.5, {easing: anime.easings['easeInOutCubic'], update: updateProgress, callback: function () {
        //     circularProgressBar.draw(0, '60%', 1, {easing: anime.easings['easeInOutCubic'], update: updateProgress, callback: function () {
        //         circularProgressBar.draw(0, '100%', 1, {delay: 0.3, easing: anime.easings.easeCircleIn, update: updateProgress, callback: completedAnimation});
        //     }});
        // }});
    }

    // Animation performed when download has been completed
    function completedAnimation() {
        // Update variables and CSS classes
        completed = true;
        buttonContainer.classList.add('completed');
        // Wait 1s for the ball animation
        setTimeout(function () {
            button.classList.add('button-hidden');
            ball.classList.add('hidden');
            borderPath.classList.remove('hidden');
            // Morphing the path to the second shape
            var morph = anime({
                targets: borderPath,
                d: 'M 40 3.5 a 36.5 36.5 0 0 0 -36.5 36.5 a 36.5 36.5 0 0 0 10.5 26.5 C 35 86.5 90 91.5 120 91.5 S 205 86.5 226 66.5 a 36.5 36.5 0 0 0 10.5 -26.5 a 36.5 36.5 0 0 0 -36.5 -36.5 Z',
                duration: 100,
                easing: 'linear',
                complete: function () {
                    // Morphing the path back to the original shape with elasticity
                    morph = anime({
                        targets: borderPath,
                        d: 'M 40 3.5 a 36.5 36.5 0 0 0 -36.5 36.5 a 36.5 36.5 0 0 0 36.5 36.5 C 70 76.5 90 76.5 120 76.5 S 170 76.5 200 76.5 a 36.5 36.5 0 0 0 36.5 -36.5 a 36.5 36.5 0 0 0 -36.5 -36.5 Z',
                        duration: 1000,
                        elasticity: 600,
                        complete: function () {
                            // Update variables and CSS classes, and return the button to the original state
                            completed = false;
                            setTimeout(function () {
                                buttonContainer.classList.remove('completed');
                                button.classList.remove('button-hidden');
                                ball.classList.remove('hidden');
                                borderPath.classList.add('hidden');
                                stopDownload();
                            }, 500);
                        }
                    });
                }
            });
        }, 1000);
    }

})();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.js
  2. https://cdnjs.cloudflare.com/ajax/libs/segment-js/1.1.2/segment.min.js