<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>
$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;
}
View Compiled
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)
    }
  }
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.