纸飞机发送按钮

点击发送后,纸飞机从按钮上飞走,文字滑成带对勾的“已发送”,随后一切复位。

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

AI 提示词

用 React 和 CSS 制作带纸飞机起飞效果的发送按钮。按钮包含一个纸飞机图标,以及叠在同一个 grid 单元格里的“发送”和“已发送”两段文字。点击后设置 data-state="sent" 并保持 2.2s,同时禁用按钮。在该状态下纸飞机播放一个 600ms 的 keyframe:先轻微后撤(translate(-4px, 4px)),然后飞到 translate(60px, -40px) rotate(-12deg) scale(0.6) 并 opacity 0,缓动 cubic-bezier(0.7, 0, 0.84, 0),让它加速飞走。“发送”向上滑出,带对勾的“已发送”在 200ms 延迟后用 300ms 从下方滑入。状态复位时纸飞机在起点淡入。用 aria-live="polite" 播报“已发送”。组件卸载时清除 timeout。在 prefers-reduced-motion 下只切换文字并隐藏纸飞机,不做飞行动画。

源代码

Demo.tsx
'use client'

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

export default function SendButton({ labels }: { labels: Record<string, string> }) {
  const [sent, setSent] = useState(false)
  const timer = useRef(0)

  useEffect(() => () => clearTimeout(timer.current), [])

  const send = () => {
    setSent(true)
    timer.current = window.setTimeout(() => setSent(false), 2200)
  }

  return (
    <button type="button" className={s.button} data-state={sent ? 'sent' : 'idle'} disabled={sent} onClick={send}>
      <svg className={s.plane} viewBox="0 0 24 24" aria-hidden="true">
        <path d="M21 3L3 10.5l7 2.5 2.5 7L21 3z" />
        <path d="M10 13l4.5-4.5" />
      </svg>
      <span className={s.labels} aria-live="polite">
        <span className={s.send}>{labels.send}</span>
        <span className={s.sent}>✓ {labels.sent}</span>
      </span>
    </button>
  )
}
demo.module.css
.button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 26px 0 22px;
  overflow: hidden;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--stage-accent);
  color: var(--stage-accent-ink);
  font: 600 var(--text-base) / 1 var(--font-body);
  white-space: nowrap;
  cursor: pointer;
}

.button:disabled { cursor: default; }
.button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }

.plane {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  transition: opacity 300ms;
}

.labels {
  display: grid;
  overflow: hidden;
}

.send, .sent {
  grid-area: 1 / 1;
  transition: transform 300ms var(--ease-out), opacity 300ms;
}

.sent { transform: translateY(110%); opacity: 0; }

.button[data-state='sent'] .plane { animation: fly 600ms var(--ease-in) forwards; }
.button[data-state='sent'] .send { transform: translateY(-110%); opacity: 0; transition-delay: 200ms; }
.button[data-state='sent'] .sent { transform: none; opacity: 1; transition-delay: 200ms; }

@keyframes fly {
  20% { transform: translate(-4px, 4px); }
  to { transform: translate(60px, -40px) rotate(-12deg) scale(0.6); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .send, .sent { transition: none; }
  .button[data-state='sent'] .plane { animation: none; opacity: 0; }
}

投稿创意

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

你想投稿的是?

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