<div class="wrapper">
  <div class="button-wrapper">
    <button id="like1" class="like"></button>
    <p>アニメーションなし</p>
  </div>
  <div class="button-wrapper">
    <button id="like2" class="like">
      <span class="circle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
      <span class="particle"></span>
    </button>
    <p>アニメーションあり</p>
  </div>
</div>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  background: #272727;
  color: #fff;
}
button {
  outline: none;
  background: none;
  border: none;
  cursor: pointer;
}

.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 40px;
  min-width: 100vw;
  min-height: 100vh;
}
.button-wrapper {
  text-align: center;
}
.like {
  width: 160px;
  height: 160px;
  position: relative;
}
.like::before,
.like::after {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  width: 50%;
  height: 80%;
  background: #ddd;
  border-radius: 40px 40px 0 0;
  transition: background 0.3s;
}
.like::before {
  transform: rotate(-45deg);
  left: 14%;
  border-bottom-left-radius: 16px;
}
.like::after {
  transform: rotate(45deg);
  right: 14%;
  border-bottom-right-radius: 16px;
}

.isLike::before,
.isLike::after {
  background: #ff669c;
}

#like2 {
  .circle {
    display: inline-block;
    position: absolute;
    top: calc(50% - 20px);
    left: calc(50% - 10px);
    width: 20px;
    height: 20px;
    border: 2px solid #fff;
    border-radius: 50%;
    z-index: 1;
    opacity: 0;
  }
  .particle {
    display: inline-block;
    position: absolute;
    top: calc(50% - 20px);
    left: calc(50% - 5px);
    width: 10px;
    height: 10px;
    transform: rotate(0deg) translateX(0px);
    border-radius: 50%;
    background: #fff;
    z-index: 1;
    opacity: 0;
  }
}
View Compiled
document.querySelectorAll('.like').forEach((el) => {
  const targetEl = document.querySelector(`#${el.id}`)

  targetEl.onclick = () => {
    targetEl.classList.toggle('isLike') // アクティブ状態のtoggle
    
    // 「アニメーションあり」だけ実行
    if(el.id === 'like2') {
      const isLike = Object.values(targetEl.classList).includes('isLike')
      likeAnim(isLike)
    }
  }
})

// アニメーション制御
const likeAnim = (isLike) => {
  if(isLike) { // いいねをつける
    bounceScaleAnim('#like2')
    expandCircleAnim('#like2 .circle')
    expandParticleAnim('.particle') 
  } else { // いいねをはずす
    bounceScaleAnim('#like2')
  }
}

// 大きさが変わるアニメーション
const bounceScaleAnim = (target) => {
  gsap.timeline()
    .to(target, { scale: 1.2, duration: 0.2 })
    .to(target, { scale: 1, duration: 0.2 })
}
// 円が広がるアニメーション
const expandCircleAnim = (target) => {
  gsap.timeline()
    .to(target, { scale: 1, opacity: 0, duration: 0.2 })
    .to(target, { scale: 4, opacity: 1, duration: 0.2 })
    .to(target, { scale: 8, opacity: 0, duration: 0.2 })
    .to(target, { scale: 1, opacity: 0, duration: 0.2 })
}
// パーティクルが広がるアニメーション
const expandParticleAnim = (target) => {
  const particles = document.querySelectorAll(target)
  particles.forEach((el, index) => {
    gsap.timeline()
      .to(el, {
        duration: 0.2,
        css: {
          transform: `rotate(${index*(360/particles.length)}deg) translateX(0px)`,
          background: index % 2 === 0 ? "#ffa3c3" : "#fffba3",
          opacity: 0
        },
      })
        .to(el, {
        duration: 0.4,
        css: {
          transform: `rotate(${index*(360/particles.length)}deg) translateX(100px)`,
          opacity: 1,
        },
      })
        .to(el, {
        duration: 0.2,
        css: {
          opacity: 0,
        },
      })
        .to(el, {
        duration: 0.2,
        css: {
          transform: `rotate(${index*(360/particles.length)}deg) translateX(0px)`,
        },
      })
  })
}


External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.co/gsap@3/dist/gsap.min.js