【问题标题】:How to fix stopwatch from incrementing when "start" is pressed?按下“开始”时如何修复秒表递增?
【发布时间】:2019-10-15 16:27:34
【问题描述】:

我创建了一个简单的秒表。它有 3 个按钮,“开始”、“停止”和“重置”。他们工作,但有一些缺陷。一旦我点击“开始”,它就会继续增加并加快时钟速度,这导致您必须多次点击“停止”才能停止时钟。

我认为这是 add 函数或 onclick 事件的问题,但我不确定如何解决。

add() 函数:

function add() {
    seconds++;
    if (seconds >= 60) {
        seconds = 0;
        minutes++;
        if (minutes >= 60) {
            minutes = 0;
            hours++;
        }
    }

    h1.textContent = (hours ? (hours > 9 ? hours : "0" + hours) : "00") + ":" + 
    (minutes ? (minutes > 9 ? minutes : "0" + minutes) : "00") + ":" + (seconds > 9 ? seconds : "0" + seconds);

    timer();    
}

点击开始按钮:

// Start
start.onclick = timer;

期望的结果是,一旦你点击开始,它应该启动时钟,无论你点击多少次,它都不应该继续增加,一旦你点击停止,它应该停止时钟。

编辑 1: setInterval()的代码:

function timer() {
    t = setInterval(add, 1000);
}

编辑 2:
“停止”和“重置”功能的代码:

// Stop 
stop.onclick = function() {
    clearInterval(t);
}

// Reset
reset.onclick = function() {
    clearInterval(t);
    h1.textContent = "00:00:00";
    seconds = 0; minutes = 0; hours = 0;
}

【问题讨论】:

  • 你在使用setInterval()吗?每次单击都会添加一个间隔,因此会触发多个回调,而不仅仅是一个。如果它已经在运行,start 应该不做任何事情或将其设置回 0 或其他值
  • @Anthony 不,我使用的是setTimeout()。我尝试使用setInterval(),但这只会成倍增加时间。
  • 您将遇到与setTimeout() 类似的问题。您能否更新问题以显示您在哪里呼叫setTimeout()
  • timer 是什么?
  • @Anthony 是的,刚刚更新。

标签: javascript time


【解决方案1】:

您可以按如下方式更改timer()

function timer() {
  if(!t) {
    t = setInterval(add, 1000)
  } else {
    clearInterval(t);
    t = null;
  }
}

这样你最终只会调用一个setInterval()

【讨论】:

  • 执行此操作时,会导致计时器仅向计时器添加 1 秒的值,然后计时器无法递增,必须重置。有没有办法让它继续增加?
  • 哎呀,你会想用setInterval,我会更新
  • 不是clearInterval()t = 0; 是什么导致了问题吗?因为else 被制定一次t 不再等于0,所以它清除了不允许它继续递增的间隔?我尝试了else { setInterval(0);},这样一旦t 不再等于0,它就会向时钟添加“无”,这可以工作,但是一旦按下“重置”,它就会导致时钟中断。
  • t 是区间句柄;通过多次调用setIntervalsetTimeout,您将创建每1000 毫秒调用的多个回调(在这种情况下)。在此更改之前,t 每次都会被重写为最新的setInterval 的句柄
  • 对,但是一旦t > 1 并且您停止时钟,“开始”就不能再被调用。单击“停止”或“开始”后如何让时钟继续运行?我将再次编辑问题,让您看到停止和重置功能;也许这可能会更有帮助。
【解决方案2】:

我真的是 javascript 的新手,所以这是我能做的最好的事情来实际制作一个工作时钟。我对python比较熟悉。

好吧,那我们就慢慢来吧

秒表:

比计算间隔中的秒、分和小时更好的方法,因此每次增量都会引入错误(JS 中的间隔不准确)让我们将秒表上的值定义为Date.now() 和 somne​​ startTime 之间的区别。

这是在时钟运行时,我们还需要在时钟不运行时存储这个value。在它开始之前,在它停止之后,......

var isRunning = false;
var value = 0;
var startTime = 0;

function getValue() {
  return isRunning ? Date.now() - startTime : value;
}

getValue() 返回时钟的毫秒数。这就是我们计算秒、分钟、小时和天所需的全部内容。

而且我们独立于任何更新间隔。每次我们调用该函数时,它都会给我们一个准确的值,无论是每帧还是每小时一次。

现在让我们添加代码来启动和停止秒表

function start() {
  if (isRunning) return;

  isRunning = true;
  // startTime = Date.now();
  // but we can do better, we can make this function to double as `restart`/`continue`
  startTime = Date.now() - value;
}

function stop() {
  if (!isRunning) return;

  isRunning = false;
  value = Date.now() - startTime;
}

// just resets the (computed) value to 0.
// does not `stop()` the stopwatch if it is running.
function reset() {
  if (isRunning) {
    startTime = Date.now();
  } else {
    value = 0;
  }
}

由于start()stop() 会检查秒表isRunning 与否,所以如果您多次调用它们没有问题。

渲染/用户界面:

// leading zero
function lz(value) {
  return (value < 10 ? "0" : "") + value;
}

function render() {
  var val = getValue(), v = Math.abs(val);

  var sign = val < 0 ? "-": "";
  var seconds = Math.floor(v/1000)%60,
      minutes = Math.floor(v/60000)%60,
      hours = Math.floor(v/3600000)%24;

  h1.textContent = sign + lz(hours) + ":" + lz(minutes) + ":" + lz(seconds);
}

最简单的方法是一直渲染值

setInterval(render, 1000);

缺点是它会在秒表不运行时一遍又一遍地呈现相同的值。但海事组织。可忽略的开销(至少对于一个间隔为 1000 毫秒的秒表而言)

或者您必须在开始和停止时添加/删除间隔:

var interval;

function start() {
  // ...
  interval = setInterval(render, 1000);
}

function stop() {
  // ...
  clearInterval(interval);
  render();
}

function reset() {
  // ...
  render();
}

总结:

var isRunning = false;
var value = 0;
var startTime = 0;
var interval;

function getValue() {
  return isRunning ? Date.now() - startTime : value;
}

function start() {
  if (isRunning) return;

  isRunning = true;
  startTime = Date.now() - value;
  interval = setInterval(render, 1000);
}

function stop() {
  if (!isRunning) return;

  isRunning = false;
  value = Date.now() - startTime;
  clearInterval(interval);
  render();
}

function reset() {
  if (isRunning) {
    startTime = Date.now();
  } else {
    value = 0;
  }
  render()
}

function lz(value) {
  return (value < 10 ? "0" : "") + value;
}

function render() {
  var val = getValue(),
    v = Math.abs(val);

  var sign = val < 0 ? "-" : "";
  var seconds = Math.floor(v / 1000) % 60,
    minutes = Math.floor(v / 60000) % 60,
    hours = Math.floor(v / 3600000) % 24;

  h1.textContent = sign + lz(hours) + ":" + lz(minutes) + ":" + lz(seconds);
}

var h1 = document.querySelector("h1");
// I know, it's cheap/dirty to select the buttons by their label
document.querySelector("input[value=start]").onclick = start;
document.querySelector("input[value=stop]").onclick = stop;
document.querySelector("input[value=reset]").onclick = reset;
render();
<h1></h1>
<input type="button" value="start" />
<input type="button" value="stop" />
<input type="button" value="reset" />

requestAnimationFrame():

var isRunning = false;
var value = 0;
var startTime = 0;

function getValue() {
  return isRunning ? Date.now() - startTime : value;
}

function start() {
  if (isRunning) return;

  isRunning = true;
  startTime = Date.now() - value;
  render();
}

function stop() {
  if (!isRunning) return;

  isRunning = false;
  value = Date.now() - startTime;
  render();
}

function reset() {
  if (isRunning) {
    startTime = Date.now();
  } else {
    value = 0;
    render();
  }
}

function lz(value) {
  return (value < 10 ? "0" : "") + value;
}

function render() {
  if (isRunning) requestAnimationFrame(render);
  var val = getValue(),
    v = Math.abs(val);

  var sign = val < 0 ? "-" : "";
  var ms = v % 1000,
    seconds = Math.floor(v / 1000) % 60,
    minutes = Math.floor(v / 60000) % 60,
    hours = Math.floor(v / 3600000) % 24;

  h1.textContent = sign + lz(hours) + ":" + lz(minutes) + ":" + lz(seconds) + "." + ms.toString().padStart(3, 0);
}

var h1 = document.querySelector("h1");
// I know, it's cheap/dirty to select the buttons by their label
document.querySelector("input[value=start]").onclick = start;
document.querySelector("input[value=stop]").onclick = stop;
document.querySelector("input[value=reset]").onclick = reset;
render();
<h1></h1>
<input type="button" value="start" />
<input type="button" value="stop" />
<input type="button" value="reset" />

因为我们从不同时使用valuestartTime;实际上它们是计算相同值的两个方面,我们可以将它们组合成一个变量offset;你可能喜欢这个“清理”,这就是为什么我只在最后添加这个。

var isRunning = false;
var offset = 0;

function getValue() {
  return isRunning ? Date.now() + offset : offset;
}

function start() {
  if (isRunning) return;

  isRunning = true;
  offset -= Date.now();
  render();
}

function stop() {
  if (!isRunning) return;

  isRunning = false;
  offset += Date.now();
  render();
}

function reset() {
  if (isRunning) {
    offset = -Date.now();
  } else {
    offset = 0;
    render();
  }
}

function lz(value) {
  return (value < 10 ? "0" : "") + value;
}

function render() {
  if (isRunning) requestAnimationFrame(render);
  var val = getValue(),
    v = Math.abs(val);

  var sign = val < 0 ? "-" : "";
  var ms = v % 1000,
    seconds = Math.floor(v / 1000) % 60,
    minutes = Math.floor(v / 60000) % 60,
    hours = Math.floor(v / 3600000) % 24;

  h1.textContent = sign + lz(hours) + ":" + lz(minutes) + ":" + lz(seconds) + "." + ms.toString().padStart(3, 0);
}

var h1 = document.querySelector("h1");
document.querySelector("input[value=start]").onclick = start;
document.querySelector("input[value=stop]").onclick = stop;
document.querySelector("input[value=reset]").onclick = reset;
render();
<h1></h1>
<input type="button" value="start" />
<input type="button" value="stop" />
<input type="button" value="reset" />

编辑:

这很好用。我添加了 clearInterval(interval);在函数 reset() 的 if 语句下 startTime = Date.now() 下,所以当按下复位时,它会停止时钟。但是,这样做后我无法重新启动时钟。有没有办法让 reset() 停止时钟,重置它,然后重新启动计时器?

您忘记更新isRunning。这样一来,您就停止了渲染值,而她的秒表实际上仍在运行。

如果这是你想要的行为,你应该使用

function reset() {
  stop();     // stop the watch, with everything what's necessary for that
  value = 0;  // reset the value
  render();   // render this changed value (once)
}

【讨论】:

  • 谢谢。这很好用。我在function reset() 的if 语句下的startTime = Date.now() 下添加了clearInterval(interval);,所以当按下重置时它会停止时钟。但是,这样做后我无法重新启动时钟。有没有办法让reset() 停止时钟,重置它,然后重新启动计时器?
  • 啊!那讲得通。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多