点赞爆发

点击爱心时,它会先压扁再弹回并填满颜色,同时向外迸出一圈小火花。

  • CSS
  • 按钮
  • 点击
  • 收录于

AI 提示词

用 CSS 为 React 制作带爆发效果的点赞按钮。按钮内放一个爱心 SVG,并切换 aria-pressed。开启时:爱心在 450ms 内按 1 → 0.8 → 1.15 → 1 缩放,缓动为 cubic-bezier(0.16, 1, 0.3, 1),填充色从透明变为强调色;同时播放一圈环绕爱心的 8 个小圆点(rotate(calc(var(--i) * 45deg)) translateY(-10px)),在 500ms 内向外移动到 translateY(-30px),透明度从 1 变为 0。每次点赞都通过改变 key 重新播放爆发。取消点赞时只清空爱心,不播放爆发。旁边用 tabular-nums 显示点赞数。aria-label 为“点赞”,点赞后更新为“已点赞”。在 prefers-reduced-motion 下跳过缩放和火花,只改变填充色。

源代码

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function LikeBurst({ labels }: { labels: Record<string, string> }) {
  const [liked, setLiked] = useState(false)
  const [burst, setBurst] = useState(0)

  const toggle = () => {
    if (!liked) setBurst(burst + 1)
    setLiked(!liked)
  }

  return (
    <button type="button" className={s.button} aria-pressed={liked} aria-label={liked ? labels.liked : labels.like} onClick={toggle}>
      <span className={s.icon}>
        <svg key={burst} className={s.heart} viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 20.5s-7.5-4.6-7.5-10.2A4.3 4.3 0 0 1 12 7.6a4.3 4.3 0 0 1 7.5 2.7c0 5.6-7.5 10.2-7.5 10.2Z" />
        </svg>
        {liked && (
          <span key={`b${burst}`} className={s.burst} aria-hidden="true">
            {Array.from({ length: 8 }, (_, i) => (
              <i key={i} style={{ '--i': i } as React.CSSProperties} />
            ))}
          </span>
        )}
      </span>
      <span className={s.count}>{liked ? 129 : 128}</span>
    </button>
  )
}
demo.module.css
.button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 20px 0 16px;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 600 var(--text-base) / 1 var(--font-body);
  cursor: pointer;
}

.button:hover { border-color: var(--stage-muted); }
.button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }

.icon { position: relative; display: grid; place-items: center; width: 26px; height: 26px; }

.heart { width: 26px; height: 26px; overflow: visible; }

.heart path {
  fill: transparent;
  stroke: var(--stage-ink);
  stroke-width: 1.8;
  transition: fill 150ms linear, stroke 150ms linear;
}

.button[aria-pressed='true'] .heart { animation: pop 450ms var(--ease-out); }
.button[aria-pressed='true'] .heart path { fill: var(--stage-accent); stroke: var(--stage-accent); }

@keyframes pop {
  0% { transform: scale(1); }
  25% { transform: scale(0.8); }
  60% { transform: scale(1.15); }
  100% { transform: scale(1); }
}

.burst { position: absolute; inset: 0; pointer-events: none; }

.burst i {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  margin: -2.5px;
  border-radius: 50%;
  background: var(--stage-accent);
  transform: rotate(calc(var(--i) * 45deg)) translateY(-10px);
  animation: spark 500ms var(--ease-out) forwards;
}

.burst i:nth-child(even) { background: var(--stage-ink); }

@keyframes spark {
  to { transform: rotate(calc(var(--i) * 45deg)) translateY(-30px); opacity: 0; }
}

.count { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .button[aria-pressed='true'] .heart { animation: none; }
  .burst { display: none; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。