Skip to content

Instantly share code, notes, and snippets.

@qtopie
Created June 14, 2025 07:36
Show Gist options
  • Select an option

  • Save qtopie/927b3960ee26112f7a706c7826ea9cf6 to your computer and use it in GitHub Desktop.

Select an option

Save qtopie/927b3960ee26112f7a706c7826ea9cf6 to your computer and use it in GitHub Desktop.
节拍器
import { Button } from '@fluentui/react-components';
import { useCallback, useEffect, useRef, useState } from 'react';
const Metronome = () => {
const [bpm, setBpm] = useState(120); // 每分钟节拍数
const [isPlaying, setIsPlaying] = useState(false); // 是否正在播放
const [volume, setVolume] = useState(50); // 音量
const audioContextRef = useRef<AudioContext | null>(null); // 音频上下文
const timerRef = useRef<number | null>(null); // 定时器引用
// 初始化 AudioContext
useEffect(() => {
if (!audioContextRef.current) {
audioContextRef.current = new AudioContext();
}
return () => {
if (audioContextRef.current) {
audioContextRef.current.close();
}
};
}, []);
// 播放节拍音效
const playTick = useCallback(() => {
if (!audioContextRef.current) return;
const oscillator = audioContextRef.current.createOscillator();
const gainNode = audioContextRef.current.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContextRef.current.destination);
oscillator.type = 'sine'; // 正弦波
gainNode.gain.setValueAtTime(volume / 100, audioContextRef.current.currentTime);
oscillator.start(audioContextRef.current.currentTime);
oscillator.stop(audioContextRef.current.currentTime + 0.1); // 播放 0.1 秒
}, [volume]);
// 启动节拍器
const startMetronome = useCallback(() => {
if (isPlaying) return;
setIsPlaying(true);
timerRef.current = window.setInterval(() => {
playTick();
}, (60 / bpm) * 1000);
}, [bpm, isPlaying, playTick]);
// 停止节拍器
const stopMetronome = useCallback(() => {
setIsPlaying(false);
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}, []);
// 切换节拍器状态
const toggleMetronome = () => {
isPlaying ? stopMetronome() : startMetronome();
};
// 清理定时器
useEffect(() => {
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
return (
<div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4">
<h1 className="text-4xl font-bold mb-6">React 节拍器</h1>
<div className="bg-gray-800 p-6 rounded-lg shadow-lg text-center">
<Button
onClick={toggleMetronome}
className={`w-full py-3 rounded-lg text-lg font-semibold ${isPlaying ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-500 hover:bg-blue-600'
}`}
>
{isPlaying ? '停止' : '启动'}
</Button>
</div>
</div>
);
};
export default Metronome;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment