首页 > 解决方案 > 高频DOM突变

问题描述

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

天文本身很容易制作,但当我试图让它快速制作时,我会出现奇怪的行为。

起初,计时器只是停留在 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`,但仍然得到相同的结果。

标签: javascriptdomsettimeout

解决方案


performance.now()您可以使用and获得更好的输出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>


推荐阅读