为何频闪控制函数内manualBPM/tapBPM变量未实时更新?
问题:频闪运行时BPM参数无法实时更新
我正在开发一款Web应用,可根据用户设置的节拍(BPM)在屏幕上显示频闪效果,希望频闪运行时也能修改控制参数,为此添加了事件监听,但遇到问题:manualBPM和tapBPM变量在频闪控制函数内无法实时更新,控制台能显示变量值正确变化,但duration变量却能正常更新。
相关代码
// manual bpm variable definition and event listener var manualBPM = 0; var manualBPM_text = document.getElementById("manual-value"); manualBPM_text.addEventListener("change", function() { manualBPM = manualBPM_text.value; }, false); // strobe duration variable definition and event listener var duration = 100; var slider = document.getElementById("MM-duration-slider"); slider.addEventListener("input", function() { duration = slider.value; }, false); var strobeActive = false; /* ----- Manual mode ON button ----- */ function MM_turnOn() { if (!manualBPM && BPMvalueSource == "manual") { window.alert("Please insert a BPM value or use the tempo tapper."); } else if (!tapBPM && BPMvalueSource == "tap") { window.alert("Please use the tempo tapper or insert a BPM value."); } else { var MM_onBtn = document.getElementById("MM-on"); var MM_offBtn = document.getElementById("MM-off"); var resetTapperBtn_a = document.getElementById("reset-tapbpm-a"); var resetTapperBtn_h = document.getElementById("reset-tapbpm-h"); // disable on button (selected) and reset tapper bpm and enable off button MM_onBtn.setAttribute("disabled", "true"); MM_offBtn.removeAttribute("disabled"); resetTapperBtn_a.setAttribute("disabled", "true"); resetTapperBtn_a.removeAttribute("onclick"); // styling MM_onBtn.classList.remove("text-gray-700"); MM_onBtn.classList.remove("hover:scale-1125"); MM_onBtn.classList.add("text-themeOrange"); MM_offBtn.classList.add("text-gray-700"); MM_offBtn.classList.add("hover:scale-1125"); MM_offBtn.classList.remove("text-themeOrange"); resetTapperBtn_h.classList.remove("cursor-pointer"); resetTapperBtn_h.classList.remove("hover:text-themeOrange"); strobeActive = true; const body = document.querySelector("body"); var interval = 0; var ranTimes = 0; if (BPMvalueSource == "manual") { interval = (60 / manualBPM) * 1000; //manualBPM not updating // repeat once the interval expires window.strobeTimeout = setInterval(function() { // trigger strobe body.classList.remove("bg-black"); body.classList.add("bg-white"); // kill strobe once the strobe duration expires setTimeout(function() { body.classList.remove("bg-white"); body.classList.add("bg-black"); }, duration); ranTimes++; console.log("BPM: " + manualBPM + " (source: " + BPMvalueSource + ") | Strobe duration: " + duration + "ms | " + "Times ran: " + ranTimes); }, interval); } else if (BPMvalueSource == "tap") { interval = (60 / tapBPM) * 1000; //tapBPM not updating // repeat once the interval expires window.strobeTimeout = setInterval(function() { // trigger strobe body.classList.remove("bg-black"); body.classList.add("bg-white"); // kill strobe once the strobe duration expires setTimeout(function() { body.classList.remove("bg-white"); body.classList.add("bg-black"); }, duration); ranTimes++; console.log("BPM: " + tapBPM + " (source: " + BPMvalueSource + ") | Strobe duration: " + duration + "ms | " + "Times ran: " + ranTimes); }, interval); } } } function killStrobe() { strobeActive = false; // function to turn off the strobe cycle clearInterval(strobeTimeout); }
问题原因
核心问题在于setInterval的间隔时间是在初始化时一次性计算并固定的,后续修改manualBPM或tapBPM不会自动更新这个间隔值。而duration能正常工作,是因为它是在每次定时器触发时才被读取的,并非固定在初始化阶段。
解决方案
推荐使用递归setTimeout替代setInterval,这样每次执行频闪前都会读取最新的BPM值计算间隔,确保参数修改后立即生效。
修改后的关键代码
var strobeActive = false; const body = document.querySelector("body"); var ranTimes = 0; // 封装递归频闪函数 function runStrobe() { if (!strobeActive) return; // 每次执行前动态计算最新的间隔 let currentInterval; if (BPMvalueSource == "manual") { currentInterval = (60 / manualBPM) * 1000; } else if (BPMvalueSource == "tap") { currentInterval = (60 / tapBPM) * 1000; } // 执行频闪逻辑 body.classList.remove("bg-black"); body.classList.add("bg-white"); setTimeout(function() { body.classList.remove("bg-white"); body.classList.add("bg-black"); }, duration); ranTimes++; console.log(`BPM: ${manualBPM} (source: ${BPMvalueSource}) | Strobe duration: ${duration}ms | Times ran: ${ranTimes}`); // 递归调用,实现动态间隔的循环 window.strobeTimeout = setTimeout(runStrobe, currentInterval); } /* ----- Manual mode ON button ----- */ function MM_turnOn() { if (!manualBPM && BPMvalueSource == "manual") { window.alert("Please insert a BPM value or use the tempo tapper."); } else if (!tapBPM && BPMvalueSource == "tap") { window.alert("Please use the tempo tapper or insert a BPM value."); } else { // 保留原有的按钮状态和样式修改逻辑 var MM_onBtn = document.getElementById("MM-on"); var MM_offBtn = document.getElementById("MM-off"); var resetTapperBtn_a = document.getElementById("reset-tapbpm-a"); var resetTapperBtn_h = document.getElementById("reset-tapbpm-h"); MM_onBtn.setAttribute("disabled", "true"); MM_offBtn.removeAttribute("disabled"); resetTapperBtn_a.setAttribute("disabled", "true"); resetTapperBtn_a.removeAttribute("onclick"); MM_onBtn.classList.remove("text-gray-700"); MM_onBtn.classList.remove("hover:scale-1125"); MM_onBtn.classList.add("text-themeOrange"); MM_offBtn.classList.add("text-gray-700"); MM_offBtn.classList.add("hover:scale-1125"); MM_offBtn.classList.remove("text-themeOrange"); resetTapperBtn_h.classList.remove("cursor-pointer"); resetTapperBtn_h.classList.remove("hover:text-themeOrange"); strobeActive = true; ranTimes = 0; // 启动递归频闪 runStrobe(); } } function killStrobe() { strobeActive = false; clearTimeout(strobeTimeout); // 改为清除setTimeout }
方案优势
- 每次频闪触发前都会读取最新的BPM值,修改参数后下一次频闪立即生效
- 避免了
setInterval可能出现的累积延迟问题 - 代码结构更清晰,逻辑分离更合理
内容的提问来源于stack exchange,提问作者SubZone
相关产品推荐
相关产品推荐

