音频跳动条

一排均衡器条以不规则的节奏跳动,表示正在播放,暂停时随之定住。

  • CSS
  • SVG 与图形
  • 循环
  • 收录于
正在播放 · 退潮

AI 提示词

用 SVG 和 CSS 制作带播放/暂停切换的均衡器动画。在 SVG 中画七个等高的圆角矩形,设置 transform-box: fill-box 和 transform-origin bottom,让它们从底线开始缩放。每根条用 alternate infinite、ease-in-out 的 keyframe 在 scaleY 0.2 到 1 之间变化,每根有自己的时长(0.45s 到 0.9s 之间)和负延迟,让节奏永远对不齐。切换按钮在外层设置 data-playing;不播放时设置 animation-play-state: paused,并让条缓缓回落到 scaleY(0.2)。按钮使用 aria-pressed,文字在“播放”和“暂停”之间切换。离开屏幕时也暂停。在 prefers-reduced-motion 下显示高度各异的静态条。说明文字为“正在播放 · 退潮”。

源代码

Demo.tsx
'use client'

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

const BARS = [0.62, 0.48, 0.81, 0.55, 0.9, 0.45, 0.7]

export default function AudioBars({ labels }: { labels: Record<string, string> }) {
  const [playing, setPlaying] = useState(true)
  const ref = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const el = ref.current
    if (!el) return
    const io = new IntersectionObserver(([e]) => el.toggleAttribute('data-offscreen', !e.isIntersecting))
    io.observe(el)
    return () => io.disconnect()
  }, [])

  return (
    <div ref={ref} className={s.player} data-playing={playing || undefined}>
      <svg viewBox="0 0 76 40" className={s.bars} aria-hidden="true">
        {BARS.map((d, i) => (
          <rect
            key={i}
            x={i * 11}
            y="0"
            width="6"
            height="40"
            rx="3"
            className={s.bar}
            style={{ '--dur': `${d}s`, '--delay': `${-i * 0.23}s`, '--rest': 0.3 + (i % 3) * 0.25 } as React.CSSProperties}
          />
        ))}
      </svg>
      <span className={s.track}>{labels.track}</span>
      <button type="button" className={s.btn} aria-pressed={playing} onClick={() => setPlaying(!playing)}>
        {playing ? labels.pause : labels.play}
      </button>
    </div>
  )
}
demo.module.css
.player {
  display: grid;
  justify-items: center;
  gap: 12px;
}

.bars { width: 96px; height: 52px; }

.bar {
  fill: var(--stage-accent);
  transform-box: fill-box;
  transform-origin: bottom;
  transform: scaleY(0.2);
  transition: transform 400ms var(--ease-out);
}

.player[data-playing] .bar {
  animation: bounce var(--dur) ease-in-out var(--delay) infinite alternate;
}

.player[data-offscreen] .bar { animation-play-state: paused; }

@keyframes bounce {
  from { transform: scaleY(0.2); }
  to { transform: scaleY(1); }
}

.track {
  font: 500 var(--text-sm) / 1 var(--font-body);
  color: var(--stage-muted);
}

.btn {
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  cursor: pointer;
}

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

@media (prefers-reduced-motion: reduce) {
  .player[data-playing] .bar { animation: none; transform: scaleY(var(--rest)); }
}

投稿创意

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

你想投稿的是?

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