添加音效
如果你倾向于尽量少使用第三方库,可以尝试用 audio 标签来播放音效。
如下方例子所示,你可以通过 react 的 Ref 来控制音频标签。
import React, { useRef } from 'react';
const SoundPlayer = () => {
const audioRef = useRef(null);
const playSound = () => {
audioRef.current.play();
};
return (
<div>
<button onClick={playSound}>Play Sound</button>
<audio ref={audioRef}>
<source src="your-sound-file.mp3" type="audio/mp3" />
Your browser does not support the audio tag.
</audio>
</div>
);
};
export default SoundPlayer;
如果你有很多音频文件要操弄的话,建议使用 howler
。
App.js 中的修改:
@@ -2,11 +2,19 @@ import { useEffect, useRef, useState } from "react";
import "./App.css";
import Settings from "./components/Settings";
import Timer from "./components/Timer";
+import { Howl, Howler } from "howler";
const POMODORO_SECONDS = 25 * 60;
const BREAK_SECONDS = 5 * 60;
const PHASE_POMODORO = 0;
const PHASE_BREAK = 1;
+
+// 音频来自 https://pixabay.com/sound-effects/search/tick-tock/
+const SOUNDS = {
+ tick: process.env.PUBLIC_URL + "/tick.mp3",
+ alarm: process.env.PUBLIC_URL + "/alarm.mp3",
+ button: process.env.PUBLIC_URL + "/button.mp3",
+};
const DEFAULT_SETTING = {
useCircle: true,
soundOn: true,
@@ -22,12 +30,29 @@ function App() {
useEffect(() => {
if (seconds === 0) {
stopTimer();
- alarm();
+ // Howler.stop() 方法的特殊处理
+ setTimeout(() => {
+ // alarm
+ playShortSound(SOUNDS.alarm);
+ }, 10);
}
}, [seconds]);
+ useEffect(() => {
+ if (ticking) {
+ playLoopSound(SOUNDS.tick);
+ } else {
+ stopSound(tickSoundIdRef.current);
+ }
+ }, [ticking]);
+
+ useEffect(() => {
+ Howler.mute(!settings.soundOn);
+ }, [settings.soundOn]);
+
// 使用 `useRef` hook 来创建一个可修改的 object,可在多次 render 中保持其值
const intervalIdRef = useRef(null);
+ const tickSoundIdRef = useRef(null);
const startTimer = () => {
setTicking(true);
@@ -49,6 +74,7 @@ function App() {
};
const toggleTimer = () => {
+ playShortSound(SOUNDS.button);
if (ticking) {
// Clicked "Pause"
stopTimer();
@@ -69,6 +95,11 @@ function App() {
return seconds / duration;
};
+ const skippable = () => {
+ const percentage = calcPercentage();
+ return percentage < 1 && percentage > 0;
+ };
+
const pickPhase = (phase) => {
const secBg = "secondary-bg";
if (phase === PHASE_POMODORO) {
@@ -81,13 +112,30 @@ function App() {
};
const skipPhase = () => {
+ playShortSound(SOUNDS.button);
const newPhase = (phase + 1) % 2;
pickPhase(newPhase);
};
- const alarm = () => {
- // TODO: 播放音效
- console.log("Time's up!");
+ const playLoopSound = (url) => {
+ const sound = new Howl({
+ src: [url],
+ loop: true,
+ });
+ tickSoundIdRef.current = sound.play();
+ };
+
+ const playShortSound = (url) => {
+ const sound = new Howl({
+ src: [url],
+ });
+ sound.play();
+ };
+
+ const stopSound = (soundId) => {
+ if (soundId) {
+ Howler.stop(soundId);
+ }
};
return (
@@ -131,7 +179,7 @@ function App() {
{ticking ? "Pause" : seconds === 0 ? "Next" : "Start"}
</button>
</div>
- <span className="skip-btn" onClick={skipPhase}>
+ <span hidden={!skippable()} className="skip-btn" onClick={skipPhase}>
skip
</span>
</div>