Check out This Cool Coin Flip Donate Button animation Using CSS and js. Designed by Cooper Goeke.
HTML
[code language=”html”]
<button class="tip-button">
<span class="tip-button__text">Send me a tip</span>
<div class="coin-wrapper">
<div class="coin">
<div class="coin__middle"></div>
<div class="coin__back"></div>
<div class="coin__front"></div>
</div>
</div>
</button>
[/code]
CSS / SCSS
[code language=”css”]
$coin-size: 3.5rem;
$coin-thickness: $coin-size / 11;
$bg: #f4f7ff;
$bg-button: #031032;
$font-color: #fff;
$c-l: #fcfaf9;
$c-m: #c2cadf;
$c-d: #8590b3;
$c-side: #737c99;
$shine: #e9f4ff;
.tip-button {
background: none;
border: 0;
border-radius: 0.25rem 0.25rem 0 0;
cursor: pointer;
font-family: ‘Quicksand’, sans-serif;
font-size: 0.75rem;
font-weight: 600;
height: 2.6rem;
margin-bottom: -4rem;
outline: 0;
position: relative;
top: 0;
transform-origin: 0% 100%;
transition: transform 50ms ease-in-out;
width: 9.5rem;
-webkit-tap-highlight-color: transparent;
&:active {
transform: rotate(4deg);
}
// Button was clicked
&.clicked {
animation: 150ms ease-in-out 1 shake;
pointer-events: none;
.tip-button__text {
opacity: 0;
transition: opacity 100ms linear 200ms;
}
&::before { // background/bar
height: 0.5rem;
width: 60%;
}
.coin {
transition: margin-bottom 1s linear 200ms;
margin-bottom: 0;
}
}
// Coin almost finished falling
&.shrink-landing {
&::before { // background/bar
transition: width 200ms ease-in;
width: 0;
}
}
// Coin finished falling
&.coin-landed {
&::after { // Thank you message
opacity: 1;
transform: scale(1);
transform-origin: 50% 100%;
}
// Make the little confetti looking dots on this wrapper
.coin-wrapper {
background:
radial-gradient(circle at 35% 97%, rgba($bg-button, 0.4) 0.04rem, transparent 0.04rem),
radial-gradient(circle at 45% 92%, rgba($bg-button, 0.4) 0.04rem, transparent 0.02rem),
radial-gradient(circle at 55% 98%, rgba($bg-button, 0.4) 0.04rem, transparent 0.04rem),
radial-gradient(circle at 65% 96%, rgba($bg-button, 0.4) 0.06rem, transparent 0.06rem);
background-position: center bottom;
background-size: 100%;
bottom: -1rem;
opacity: 0;
transform: scale(2) translateY(-10px);
}
}
&__text {
color: $font-color;
margin-right: 1.8rem;
opacity: 1;
position: relative;
transition: opacity 100ms linear 500ms;
z-index: 3;
}
// Background of button
&::before {
background: $bg-button;
border-radius: 0.25rem;
bottom: 0;
content: ”;
display: block;
height: 100%;
left: 50%;
position: absolute;
transform: translateX(-50%);
transition: height 250ms ease-in-out 400ms, width 250ms ease-in-out 300ms;
width: 100%;
z-index: 2;
}
// Thank you message
&::after {
bottom: -1rem;
color: $bg-button;
content: ‘Thank you!’;
height: 110%;
left: 0;
opacity: 0;
position: absolute;
pointer-events: none;
text-align: center;
transform: scale(0);
transform-origin: 50% 20%;
transition: transform 200ms cubic-bezier(0,0,.35,1.43);
width: 100%;
z-index: 1;
}
}
.coin-wrapper {
background: none;
bottom: 0;
height: 18rem;
left: 0;
opacity: 1;
overflow: hidden;
pointer-events: none;
position: absolute;
transform: none;
transform-origin: 50% 100%;
transition: opacity 200ms linear 100ms, transform 300ms ease-out;
width: 100%;
}
.coin {
–front-y-multiplier: 0;
–back-y-multiplier: 0;
–coin-y-multiplier: 0;
–coin-x-multiplier: 0;
–coin-scale-multiplier: 0;
–coin-rotation-multiplier: 0;
–shine-opacity-multiplier: 0.4;
–shine-bg-multiplier: 50%;
bottom: calc(var(–coin-y-multiplier) * 1rem – #{$coin-size});
height: $coin-size;
margin-bottom: 3.05rem;
position: absolute;
right: calc(var(–coin-x-multiplier) * 34% + 16%);
transform:
translateX(50%)
scale(calc(0.4 + var(–coin-scale-multiplier)))
rotate(calc(var(–coin-rotation-multiplier) * -1deg));
transition: opacity 100ms linear 200ms;
width: $coin-size;
z-index: 3;
&__front,
&__middle,
&__back,
&::before,
&__front::after,
&__back::after {
border-radius: 50%;
box-sizing: border-box;
height: 100%;
left: 0;
position: absolute;
width: 100%;
z-index: 3;
}
// Tails
&__front {
background:
radial-gradient(circle at 50% 50%, transparent 50%, rgba($c-side, 0.4) 54%, $c-m 54%),
linear-gradient(210deg, $c-d 32%, transparent 32%),
linear-gradient(150deg, $c-d 32%, transparent 32%),
linear-gradient(to right, $c-d 22%, transparent 22%, transparent 78%, $c-d 78%),
linear-gradient(to bottom, $c-l 44%, transparent 44%, transparent 65%, $c-l 65%, $c-l 71%, $c-d 71%),
linear-gradient(to right, transparent 28%, $c-l 28%, $c-l 34%, $c-d 34%, $c-d 40%, $c-l 40%, $c-l 47%, $c-d 47%, $c-d 53%, $c-l 53%, $c-l 60%, $c-d 60%, $c-d 66%, $c-l 66%, $c-l 72%, transparent 72%);
background-color: $c-d;
background-size: 100% 100%;
transform: translateY(calc(var(–front-y-multiplier) * #{$coin-thickness} / 2)) scaleY(var(–front-scale-multiplier));
// Shadow on coin face
&::after {
background: rgba(#000, 0.2);
content: ”;
opacity: var(–front-y-multiplier);
}
}
&__middle {
background: $c-side;
transform: translateY(calc(var(–middle-y-multiplier) * #{$coin-thickness} / 2)) scaleY(var(–middle-scale-multiplier));
}
// Heads
&__back {
background:
radial-gradient(circle at 50% 50%, transparent 50%, rgba($c-side, 0.4) 54%, $c-m 54%),
radial-gradient(circle at 50% 40%, $c-l 23%, transparent 23%),
radial-gradient(circle at 50% 100%, $c-l 35%, transparent 35%);
background-color: $c-d;
background-size: 100% 100%;
transform: translateY(calc(var(–back-y-multiplier) * #{$coin-thickness} / 2)) scaleY(var(–back-scale-multiplier));
// Shadow on coin face
&::after {
background: rgba(#000, 0.2);
content: ”;
opacity: var(–back-y-multiplier);
}
}
// Light glare on the coin
&::before {
background:
radial-gradient(circle at 25% 65%, transparent 50%, rgba(white, 0.9) 90%),
linear-gradient(55deg, transparent calc(var(–shine-bg-multiplier) + 0%), $shine calc(var(–shine-bg-multiplier) + 0%), transparent calc(var(–shine-bg-multiplier) + 50%));
content: ”;
opacity: var(–shine-opacity-multiplier);
transform:
translateY(calc(var(–middle-y-multiplier) * #{$coin-thickness} / -2))
scaleY(var(–middle-scale-multiplier))
rotate(calc(var(–coin-rotation-multiplier) * 1deg));
z-index: 10;
}
// Sqaure for the ‘side’ of the coin
&::after {
background: $c-side;
content: ”;
height: $coin-thickness;
left: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 2;
}
}
@keyframes shake {
0% { transform: rotate(4deg) }
66% { transform: rotate(-4deg) }
100% { transform: rotate() }
}
/********* BODY STYLES *********/
html,
body {
height: 100%;
}
body {
align-items: center;
background: $bg;
display: flex;
justify-content: center;
-webkit-font-smoothing: antialiased;
}
[/code]
JS
[code language=”js”]
const tipButtons = document.querySelectorAll(‘.tip-button’)
// Loop through all buttons (allows for multiple buttons on page)
tipButtons.forEach((button) => {
let coin = button.querySelector(‘.coin’)
// The larger the number, the slower the animation
coin.maxMoveLoopCount = 90
button.addEventListener(‘click’, () => {
if (button.clicked) return
button.classList.add(‘clicked’)
// Wait to start flipping the coin because of the button tilt animation
setTimeout(() => {
// Randomize the flipping speeds just for fun
coin.sideRotationCount = Math.floor(Math.random() * 5) * 90
coin.maxFlipAngle = (Math.floor(Math.random() * 4) + 3) * Math.PI
button.clicked = true
flipCoin()
}, 50)
})
const flipCoin = () => {
coin.moveLoopCount = 0
flipCoinLoop()
}
const resetCoin = () => {
coin.style.setProperty(‘–coin-x-multiplier’, 0)
coin.style.setProperty(‘–coin-scale-multiplier’, 0)
coin.style.setProperty(‘–coin-rotation-multiplier’, 0)
coin.style.setProperty(‘–shine-opacity-multiplier’, 0.4)
coin.style.setProperty(‘–shine-bg-multiplier’, ‘50%’)
coin.style.setProperty(‘opacity’, 1)
// Delay to give the reset animation some time before you can click again
setTimeout(() => {
button.clicked = false
}, 300)
}
const flipCoinLoop = () => {
coin.moveLoopCount++
let percentageCompleted = coin.moveLoopCount / coin.maxMoveLoopCount
coin.angle = -coin.maxFlipAngle * Math.pow((percentageCompleted – 1), 2) + coin.maxFlipAngle
// Calculate the scale and position of the coin moving through the air
coin.style.setProperty(‘–coin-y-multiplier’, -11 * Math.pow(percentageCompleted * 2 – 1, 4) + 11)
coin.style.setProperty(‘–coin-x-multiplier’, percentageCompleted)
coin.style.setProperty(‘–coin-scale-multiplier’, percentageCompleted * 0.6)
coin.style.setProperty(‘–coin-rotation-multiplier’, percentageCompleted * coin.sideRotationCount)
// Calculate the scale and position values for the different coin faces
// The math uses sin/cos wave functions to similate the circular motion of 3D spin
coin.style.setProperty(‘–front-scale-multiplier’, Math.max(Math.cos(coin.angle), 0))
coin.style.setProperty(‘–front-y-multiplier’, Math.sin(coin.angle))
coin.style.setProperty(‘–middle-scale-multiplier’, Math.abs(Math.cos(coin.angle), 0))
coin.style.setProperty(‘–middle-y-multiplier’, Math.cos((coin.angle + Math.PI / 2) % Math.PI))
coin.style.setProperty(‘–back-scale-multiplier’, Math.max(Math.cos(coin.angle – Math.PI), 0))
coin.style.setProperty(‘–back-y-multiplier’, Math.sin(coin.angle – Math.PI))
coin.style.setProperty(‘–shine-opacity-multiplier’, 4 * Math.sin((coin.angle + Math.PI / 2) % Math.PI) – 3.2)
coin.style.setProperty(‘–shine-bg-multiplier’, -40 * (Math.cos((coin.angle + Math.PI / 2) % Math.PI) – 0.5) + ‘%’)
// Repeat animation loop
if (coin.moveLoopCount < coin.maxMoveLoopCount) {
if (coin.moveLoopCount === coin.maxMoveLoopCount – 6) button.classList.add(‘shrink-landing’)
window.requestAnimationFrame(flipCoinLoop)
} else {
button.classList.add(‘coin-landed’)
coin.style.setProperty(‘opacity’, 0)
setTimeout(() => {
button.classList.remove(‘clicked’, ‘shrink-landing’, ‘coin-landed’)
setTimeout(() => {
resetCoin()
}, 300)
}, 1500)
}
}
})
[/code]