#zzButtonProgress
View Compiled
@import url("https://fonts.googleapis.com/css?family=Raleway:400,400i,700");

body {
  font-family: Raleway, sans-serif;
  background-color: #424242;
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.zz-button-progress {
  $full-background-color: #2f8aff;
  $empty-background-color: #085dc9;
  --progress: 100%;
  color: #ffffff;
  font-family: inherit;
	font-weight: bold;
  background-color: $full-background-color;
  background-image: linear-gradient(
    to top,
    $full-background-color 0 var(--progress),
    $empty-background-color var(--progress) 100%
  );
	border: 0;
	outline: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
	cursor: pointer;
  border-top-left-radius: 20% 50%;
  border-top-right-radius: 20% 50%;
  border-bottom-left-radius: 20% 50%;
  border-bottom-right-radius: 20% 50%;
  span {
    font-family: inherit;
  }
}

.zz-check {
  --width: 0px;
  --height: 0px;
  --thickness: 0px;
  --left-height: 0%;
  --bottom-width: 0%;
  width: var(--width);
  height: var(--height);
  display: none;
  position: absolute;
  top: calc(50% - calc(var(--height) / 2));
  left: calc(50% - calc(var(--width) / 2));
  transform: translateY(-25%) rotate(-45deg);
  &:before, &:after {
    content: "";
    background-color: #ffffff;
    display: block;
    position: absolute;
  }
  &:before {
    width: var(--thickness);
    height: var(--left-height);
    top: 0;
    left: 0;
  }
  &:after {
    width: var(--bottom-width);
    height: var(--thickness);
    bottom: 0;
    left: 0;
  }
}
View Compiled
import React, { useState } from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";
import gsap from "https://cdn.skypack.dev/gsap@3.6.1";

// Constants
const START_DURATION = 0.75;
const END_DURATION = 1;

// GSAP timeline
const tl = gsap.timeline();
  
/**
 * Start animation for ZZ Button Progress
 */
function animateZzButtonProgressStart(elms, props, sizeAvg, callback) {
  // The button will become circle
  tl.to(elms.buttonElm, {
    width: `${sizeAvg}px`,
    height: `${sizeAvg}px`,
    borderTopLeftRadius: "50%",
    borderTopRightRadius: "50%",
    borderBottomLeftRadius: "50%",
    borderBottomRightRadius: "50%",
    ease: "elastic.out(1.75, 0.3)",
    duration: START_DURATION
  });
  
  // Make the text size of 'upload' button smaller
  tl.to(elms.uploadTextElm, {
    fontSize: `${parseInt(props.fontSize) / 2}px`,
    ease: "elastic.out(1.75, 0.3)",
    duration: START_DURATION,
  }, "-=" + START_DURATION);
  
  // Make the text size of progress count larger
  tl.to(elms.progressTextElm, {
    fontSize: `${parseInt(props.fontSize) * 1.5}px`,
    ease: "elastic.out(1.75, 0.3)",
    duration: START_DURATION,
  }, "-=" + START_DURATION);
  
  // Darken the background color of the button
  tl.to(elms.buttonElm, {
    "--progress": "0%",
    ease: "power2.out",
    duration: START_DURATION,
    onComplete() {
      callback();
    }
  }, "-=" + START_DURATION);
}

/**
 * End animation for ZZ Button Progress
 */
function animateZzButtonProgressEnd(elms, props, callback) {
  const halfDuration = END_DURATION / 2;
  const quarterDuration = END_DURATION / 4;
  
  // Return to original shape
  tl.to(elms.buttonElm, {
    width: `${props.width}px`,
    height: `${props.height}px`,
    borderTopLeftRadius: "20% 50%",
    borderTopRightRadius: "20% 50%",
    borderBottomLeftRadius: "20% 50%",
    borderBottomRightRadius: "20% 50%",
    ease: "elastic.out(1.75, 0.3)",
    delay: 0.25,
    duration: halfDuration
  });
  
  // Turn into green background
  tl.to(elms.buttonElm, {
    backgroundColor: "#52b500",
    ease: "power2.out",
    duration: halfDuration
  }, "-=" + halfDuration);
  
  // Put the font opacity into transparent
  tl.to(elms.uploadTextElm, {
    opacity: 0,
    ease: "power2.out",
    duration: halfDuration
  }, "-=" + halfDuration);
  
  tl.to(elms.progressTextElm, {
    opacity: 0,
    ease: "power2.out",
    duration: halfDuration
  }, "-=" + halfDuration);
  
  // HACK: Set the background image to none
  //       This is to make background color transition work
  tl.set(elms.buttonElm, {
    backgroundImage: "none"
  }, "-=" + halfDuration);
  
  // Set the display of check container into block
  tl.set(elms.checkElm, {
    display: "block"
  });
  
  // Left check border
  tl.to(elms.checkElm, {
    "--left-height": "100%",
    ease: "elastic.out(2, 0.5)",
    duration: quarterDuration
  });
  
  // Bottom check border
  tl.to(elms.checkElm, {
    "--bottom-width": "100%",
    ease: "elastic.out(2, 0.5)",
    duration: quarterDuration
  });
  
  // HACK: This is to add delay before returning to original state
  tl.to(elms.buttonElm, {
    duration: 2,
    onComplete() {
      // tl.set(elms.buttonElm, {
      //   backgroundColor: "transparent"
      // });
      
      callback();
    }
  });
}

/**
 * ZZ Button Progress JSX Element
 */
function ZzButtonProgress(props) {
  if (isNaN(props.fontSize) || isNaN(props.width) || isNaN(props.height)) {
    return <span>Not seems to be working</span>;
  }
  
  // Flag if the button is clicked
  const [clicked, setClicked] = useState(false);
  
  // Progress count of upload
  const [progressText, setProgressText] = useState(100);
  
  // Get the average value of width and height
  const width = parseInt(props.width);
  const height = parseInt(props.height);
  const sizeAvg = Math.round((width + height) / 2);
  
  // Set the size of the check based on the height
  const checkThickness = height / 10;
  const checkWidth = (height / 2) + checkThickness;
  const checkHeight = (height / 5) + checkThickness;
  
  // Inline style of the upload button itself
  const buttonStyle = {
    "--progress": `${progressText}%`,
    width: `${width}px`,
    height: `${height}px`
  };
  
  // Inline style of the 'Upload' text
  const uploadTextStyle = {
    fontSize: `${props.fontSize}px`
  };
  
  // Inline style of the progress count percentage
  const progressTextStyle = {
    fontSize: `0px`
  };
  
  // Inline style of the 'check' after upload
  const checkStyle = {
    "--width": `${checkWidth}px`,
    "--height": `${checkHeight}px`,
    "--thickness": `${checkThickness}px`
  }
  
  return (
    <button
      className="zz-button-progress"
      style={buttonStyle}
      onClick={evt => {
        if (clicked === true) {
          // Prevent double action
          
          return;
        }
        
        setClicked(true);
        setProgressText(0);
        
        // JSX elements to be animated
        const elms = {
          buttonElm: evt.target,
          uploadTextElm: evt.target.querySelector("span:nth-child(1)"),
          progressTextElm: evt.target.querySelector("span:nth-child(2)"),
          checkElm: evt.target.querySelector(".zz-check")
        };
        
        // Start animation for ZZ Button Progress
        animateZzButtonProgressStart(elms, props, sizeAvg, () => {
          // Callback after 'start' animation
          
          // Initialise the progress used by GSAP
          let progress = {
            count: 0
          };
          
          // Change the progress count using GSAP
          gsap.to(progress, {
            count: 100,
            ease: "circ.inOut",
            duration: 1.5,
            onUpdate() {
              setProgressText(Math.floor(progress.count));
            },
            onComplete() {
              // End animation for ZZ Button Progress
              animateZzButtonProgressEnd(elms, props, () => {
                // Callback after 'end' animation
                
                setClicked(false);

                // Reset the animation to beginning
                tl.progress(0);
                tl.clear();
                
                // HACK: Remove the property of background-image
                gsap.set(elms.buttonElm, {
                  backgroundImage: null
                });
              });
            }
          });
        });
      }}>
        <span
          style={uploadTextStyle}
          onClick={evt => {
            evt.stopPropagation();
            evt.target.parentElement.click();
        }}>
          {props.text}
        </span>
        <span style={progressTextStyle} onClick={evt => { evt.stopPropagation(); }}>
          {progressText}%
        </span>
        <span className="zz-check" style={checkStyle}></span>
      </button>
  );
}

ReactDOM.render(
  <ZzButtonProgress text="Upload" fontSize="32" width="250" height="100" />,
  document.querySelector("#zzButtonProgress")
);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.