<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)`,
},
})
})
}
This Pen doesn't use any external CSS resources.