426呼吸法计时器
–
0 秒
const PHASES = {
inhale: { name: ‘吸气’, duration: 4000, displayName: ‘吸 气’ },
hold: { name: ‘屏气’, duration: 2000, displayName: ‘屏 气’ },
exhale: { name: ‘呼气’, duration: 6000, displayName: ‘呼 气’ }
};
const phaseOrder = [‘inhale’, ‘hold’, ‘exhale’];
let isRunning = false;
let currentPhaseIndex = 0;
let cycleCount = 0;
let startTime = null;
let elapsedBeforeStart = 0;
let animationFrameId = null;
const startBtn = document.getElementById(‘startBtn’);
const endBtn = document.getElementById(‘endBtn’);
const cycleCountDisplay = document.getElementById(‘cycleCount’);
const phaseNameDisplay = document.getElementById(‘phaseName’);
const phaseProgressDisplay = document.getElementById(‘phaseProgress’);
const progressFill = document.getElementById(‘progressFill’);
const currentPhaseDisplay = document.getElementById(‘currentPhase’);
const elapsedTimeDisplay = document.getElementById(‘elapsedTime’);
const statusText = document.getElementById(‘statusText’);
const emotionDisplay = document.getElementById(’emotionDisplay’);
const emotionMap = {
inhale: { emoji: ‘😤’, label: ‘吸气中’ },
hold: { emoji: ‘😑’, label: ‘屏气中’ },
exhale: { emoji: ‘😌’, label: ‘呼气中’ }
};
startBtn.addEventListener(‘click’, startBreathing);
endBtn.addEventListener(‘click’, endBreathing);
function startBreathing() {
isRunning = true;
startTime = Date.now();
currentPhaseIndex = 0;
cycleCount = 0;
elapsedBeforeStart = 0;
startBtn.disabled = true;
endBtn.disabled = false;
statusText.textContent = ”;
phaseNameDisplay.textContent = PHASES[phaseOrder[0]].displayName;
emotionDisplay.textContent = emotionMap[‘inhale’].emoji;
emotionDisplay.className = ’emotion-display inhale’;
animate();
}
function endBreathing() {
isRunning = false;
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
startBtn.disabled = false;
endBtn.disabled = true;
statusText.textContent = `✓ 本次训练完成,共完成 ${cycleCount} 组呼吸`;
phaseNameDisplay.textContent = ‘训练已结束’;
phaseProgressDisplay.textContent = ‘-‘;
currentPhaseDisplay.textContent = ‘-‘;
progressFill.style.width = ‘0%’;
emotionDisplay.textContent = ‘😊’;
emotionDisplay.className = ’emotion-display’;
}
function animate() {
if (!isRunning) return;
const now = Date.now();
const totalElapsed = now – startTime + elapsedBeforeStart;
updateDisplay(totalElapsed);
updateElapsedTime(totalElapsed);
animationFrameId = requestAnimationFrame(animate);
}
function updateDisplay(totalElapsed) {
// 一个完整的循环:吸气4秒 + 屏气2秒 + 呼气6秒 = 12秒
const cycleDuration = 4000 + 2000 + 6000; // 12秒
// 计算完成的组数
cycleCount = Math.floor(totalElapsed / cycleDuration);
// 计算在当前循环中的位置
const timeInCurrentCycle = totalElapsed % cycleDuration;
let phase = ‘inhale’;
let timeInCurrentPhase = timeInCurrentCycle;
// 判断当前处于哪个阶段
if (timeInCurrentPhase < 4000) {
phase = 'inhale';
} else if (timeInCurrentPhase < 6000) {
phase = 'hold';
timeInCurrentPhase -= 4000;
} else {
phase = 'exhale';
timeInCurrentPhase -= 6000;
}
const phaseInfo = PHASES[phase];
const progress = (timeInCurrentPhase / phaseInfo.duration * 100).toFixed(1);
const remaining = Math.ceil((phaseInfo.duration – timeInCurrentPhase) / 1000);
phaseNameDisplay.textContent = phaseInfo.displayName;
phaseProgressDisplay.textContent = remaining;
currentPhaseDisplay.textContent = phaseInfo.name;
progressFill.style.width = progress + '%';
// 更新表情和动画
emotionDisplay.textContent = emotionMap[phase].emoji;
emotionDisplay.className = `emotion-display ${phase}`;
}
function updateElapsedTime(totalElapsed) {
const seconds = Math.floor(totalElapsed / 1000);
elapsedTimeDisplay.textContent = `${seconds} 秒`;
}