高频 DOM 突变

High frequency DOM mutations

我已经构建了一个简单的计时器,但我希望它尽可能 快速准确。换句话说,我想要最好的刷新率,同时保持其精度,精度是指秒的数字应每 1000 毫秒更改一次,分钟应每 60 秒更改一次,等等。

chronometer 本身很容易制作,但当我尝试快速制作时,我会遇到奇怪的行为。

一开始,计时表只是停留在00:00,甚至不会改变。但我很快意识到这是因为我没有给浏览器任何时间来在每次执行更新和格式化时间的函数之间呈现元素。所以我试着让它在 0 毫秒超时后进行每次下一次更新。这样,我认为它会将下一次更新的执行放在执行堆栈的末尾,以便浏览器在执行下一次更新功能之前呈现元素。

超时解决了我的问题,但只是部分解决了。天文台表似乎工作正常,直到我意识到经常跳过一秒钟。

我尝试在超时中添加大约 5 毫秒的小延迟,但仍然跳过了秒,尽管每个跳过的秒之间的延迟更大。我尝试玩超时延迟,但即使延迟超过 50 毫秒,秒数仍然会被跳过。我什至尝试使用 queueMicrotask 函数并将其与超时结合使用,但它没有帮助。

在这一点上,我试图找到另一种解决方案,但我找不到任何东西。我不明白为什么会这样,所以如果有人能向我解释发生了什么以及我该如何解决这个问题,我将不胜感激。

注意:每次跳过秒之间的延迟不是恒定的,由于某种原因,1 分钟后跳过秒的频率更高。

时计

const START_TIME = Date.now();

function formatTime(ms) {
  ms = Math.round(ms * 1);
  let secs = Math.trunc(ms / 1e3);
  ms = (ms / 1e3 - secs) * 1e3;

  let mins = Math.trunc(secs / 60);
  secs = (secs / 60 - mins) * 60;

  let hours = Math.trunc(mins / 60);
  mins = (mins / 60 - hours) * 60;

  hours = String(Math.trunc(hours));
  mins = String(Math.trunc(mins));
  secs = String(Math.trunc(secs));
  ms = String(Math.trunc(ms));

  const pad = (t, i = 2) => t.padStart(i, "0");
  return `${pad(hours)}:${pad(mins)}:${pad(secs)},${pad(ms, 3)}`;
}

(() => {
  const el = document.querySelector('.time');

  const update = () => {
    el.innerText = formatTime(Date.now() - START_TIME);
    setTimeout(update);
  }
  update();
})();
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap");
body {
  background-color: #222;
  color: #35E;
  margin: 0;
  padding: 0;
}

.wrapper {
  font-family: "Inconsolata", monospace;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  font-size: calc(4vw + 4vh + 10vmin);
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="wrapper">
  <div class="time"></div>
</div>


更新:
我尝试使用“requestAnimationFrame”,但仍然得到相同的结果。

您可以使用 performance.now()requestAnimationFrame

获得更好的输出

const START_TIME = performance.now();

function formatTime(ms) {
  ms = Math.round(ms * 1);
  let secs = Math.trunc(ms / 1e3);
  ms = (ms / 1e3 - secs) * 1e3;

  let mins = Math.trunc(secs / 60);
  secs = (secs / 60 - mins) * 60;

  let hours = Math.trunc(mins / 60);
  mins = (mins / 60 - hours) * 60;

  hours = String(Math.trunc(hours));
  mins = String(Math.trunc(mins));
  secs = String(Math.trunc(secs));
  ms = String(Math.trunc(ms));

  const pad = (t, i = 2) => t.padStart(i, "0");
  return `${pad(hours)}:${pad(mins)}:${pad(secs)},${pad(ms, 3)}`;
}

(() => {
  const el = document.querySelector('.time');

  const update = () => {
    el.innerText = formatTime(performance.now() - START_TIME);
    requestAnimationFrame(update);
  }
  update();
})();
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap");
body {
  background-color: #222;
  color: #35E;
  margin: 0;
  padding: 0;
}

.wrapper {
  font-family: "Inconsolata", monospace;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  font-size: calc(4vw + 4vh + 10vmin);
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="wrapper">
  <div class="time"></div>
</div>

我个人会这样计算时间

const START_TIME = performance.now();

function formatTime(duration) {
  let milliseconds = parseInt((duration % 1000));
  let seconds = Math.floor((duration / 1000) % 60);
  let minutes = Math.floor((duration / (1000 * 60)) % 60);
  let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);

  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;
  milliseconds = (milliseconds < 10) ? "00" + milliseconds : milliseconds < 100 ? "0" + milliseconds : milliseconds;

  return hours + ":" + minutes + ":" + seconds + "," + milliseconds;
}

(() => {
  const el = document.querySelector('.time');

  const update = () => {
    el.textContent = formatTime(performance.now() - START_TIME);
    requestAnimationFrame(update);
  }
  update();
})();
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap");
body {
  background-color: #222;
  color: #35E;
  margin: 0;
  padding: 0;
}

.wrapper {
  font-family: "Inconsolata", monospace;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  font-size: calc(4vw + 4vh + 10vmin);
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="wrapper">
  <div class="time"></div>
</div>

数学不好 - 你在累积错误。我没有在下面累积任何错误,因为我一直在ms.

上操作

const START_TIME = Date.now();

function formatTime(diff) {
  // ms are always the last three digits
  const ms = ((diff / 1000 - Math.trunc(diff / 1000)) * 1000)
    .toFixed(0)
    .padStart(3, 0);

  // seconds are always the integer part of the diff when divided by 1000 - mod 60 here the number "wraps" when it gets to 60
  const seconds = (Math.trunc(diff / 1000) % 60).toFixed(0).padStart(2, 0);
  // minutes are always the integer part of the diff when divided by 1000*60 - mod 60 again
  const minutes = (Math.trunc(diff / (1000 * 60)) % 60)
    .toFixed(0)
    .padStart(2, 0);
 
  // hours are the integer part of the diff when divided by 1000*60*60
  const hours = Math.trunc(diff / (1000 * 60 * 60))
    .toFixed(0)
    .padStart(2, 0);
  return `${hours}:${minutes}:${seconds}:${ms}`;
}

(() => {
  const el = document.querySelector('.time');

  const update = () => {
    el.innerText = formatTime(Date.now() - START_TIME);
    setTimeout(update);
  }
  update();
})();
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap");
body {
  background-color: #222;
  color: #35E;
  margin: 0;
  padding: 0;
}

.wrapper {
  font-family: "Inconsolata", monospace;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  font-size: calc(4vw + 4vh + 10vmin);
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="wrapper">
  <div class="time"></div>
</div>