#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
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.