时钟和倒计时:CSS 和 JavaScript 中的计时

2025-06-07

时钟和倒计时:CSS 和 JavaScript 中的计时

有一天我需要一个数字时钟组件,所以我很快编写了一个简单的 JavaScript 方法:

function uiDigitalClock(node) {
  const now = () => {
    node.textContent = new Date().toLocaleTimeString()
    requestAnimationFrame(now)
  }
  now()
}
Enter fullscreen mode Exit fullscreen mode

我在某处读到过,其requestAnimationFrame性能应该比更好setInterval,但这种方法一直困扰着我。

在最佳条件下,每秒requestAnimationFrame将触发60 次- 这是数字时钟所需次数的 60 倍!

Date()每秒创建 60 次对象对于性能来说肯定是不利的!

所以我尝试了setInterval

function interval(node){
  return setInterval(() => node.textContent = new Date().toLocaleTimeString(), 1000)
}
Enter fullscreen mode Exit fullscreen mode

同样,代码不多,视觉效果是一样的——每秒只有一个对象。 Date()

怎么样setTimeout()

function timeout(node) {
  const now = () => {
    node.textContent = new Date().toLocaleTimeString()
    setTimeout(now, 1000)
  }
  now()
}
Enter fullscreen mode Exit fullscreen mode

它也可以得到类似的结果,但有一个恼人的延迟(setImmediate()仅在 Node 中)

然后我决定重新设计初始版本,使其每秒触发一次,并requestAnimationFrame()返回一个时间戳:

function frame(node) {
  let last = 0;
  const render = now => {
    if (!last || now - last >= 1000) {
      last = now;
      node.textContent = new Date().toLocaleTimeString();
    }
    requestAnimationFrame(render);
  }
  window.requestAnimationFrame(render);
}
Enter fullscreen mode Exit fullscreen mode

在所有 3 种方法运行时,我碰巧检查了 Dev Tools 中的标记,并发现我甚至无法展开节点,因为它们不断被刷新,看起来就像旧的<blink>-tag!


向巴德寻求帮助

仍然不知道该选择哪种方法,我决定询问 Google Bard,它回答说:

  • clock1使用了该setInterval()方法,该方法创建一个每 1000 毫秒(1 秒)触发一次的循环计时器。这是性能最差的方法,因为它为每次迭代都创建一个新的计时器,这会增加相当大的开销。

  • clock2使用 该setTimeout()方法,该方法创建一个在 1000 毫秒后触发的计时器。由于它不会在每次迭代时创建新的计时器,因此其性能比 clock1 更高。然而,它的性能仍然不如 clock3,因为浏览器必须等待计时器触发后才能更新文本内容。

  • clock3使用requestAnimationFrame()方法来请求浏览器的重绘,这是一种更高效的方式。浏览器只会在必要时重绘元素,从而节省大量的 CPU 时间。

好吧,我决定使用clock3 ......但后来我突然想到:“那么在 CSS 中使用数字动画@property怎么样?

Jhey不久前制作了一个纯 CSS 秒表,所以我决定尝试一些类似的东西。

@property --seconds {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
@property --minutes {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
@property --hours {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
Enter fullscreen mode Exit fullscreen mode

然后,在<ol>-tag 中,我<li>为每个时间单位添加了一个 -tag。

要使用 -declaration 的值@property,您需要使用 CSS 计数器,因此秒数为:

.seconds {
  animation: seconds 60s steps(60, end) infinite;
  animation-delay: var(--delay-seconds, 0s);
  counter-reset: seconds var(--seconds);
  &::after { content: counter(seconds, decimal-leading-zero) ' '; }
}
Enter fullscreen mode Exit fullscreen mode

制作秒数动画,需要一个关键帧:

@keyframes seconds { 
  from { --seconds: 0;}
  to { --seconds: 60; }
}
Enter fullscreen mode Exit fullscreen mode

就分钟数而言,几乎相同,但动画耗时却长了 60 倍(60 x 60 = 3600):

animation: minutes 3600s steps(60, end) infinite;
Enter fullscreen mode Exit fullscreen mode

对于小时数,我们需要将该数字乘以 24:

animation: hours 86400s steps(24, end) infinite;
Enter fullscreen mode Exit fullscreen mode

耶!我们终于有了一个能用的 CSS 时钟……不过它只在午夜工作,因为时、分、秒都是从零开始的0

那么该怎么办呢?创建初始对象后,我可以轻松地从 JavaScript 中更新属性Date()

但这样动画就会出错,因为它们会运行相同的时间(60 秒),即使实际的秒数少于这个时间。

我在推特上寻求帮助——幸运的是,Temani Afif 和 Álvaro Montoro 回复了!解决办法是使用否定词 animation-delay

因此,使用一些 JavaScript 来设置当前时间并计算延迟:

const time = new Date();
const hours = time.getHours();
const minutes = time.getMinutes();
const seconds = time.getSeconds();

// Delays
const HOURS = -Math.abs((hours * 3600) + (minutes * 60) + seconds);
const MINS = -Math.abs((minutes * 60) + seconds);
const SECS = -Math.abs(seconds);
Enter fullscreen mode Exit fullscreen mode

...我们可以更新之前指定的 CSS 属性,例如:

node.style.setProperty(`--delay-seconds`, `${seconds}s`);
Enter fullscreen mode Exit fullscreen mode

现在,我们有一个可以工作的数字 CSS 时钟——将它与此处的其他方法进行比较:

如果您检查开发工具中的标记,您会发现 CSS 版本没有重写 DOM 内容。


倒计时

在此之后,我决定重新审视我的旧 Codepen、多语言倒计时,并制作一个仅 CSS 版本:

locale如果您想要使用其他语言,您可以在 JS 代码中尝试一下:

CSS倒计时

但是性能呢?CSS 可能不会像 JavaScript 那样阻塞主线程,但我们能确定它使用的是 GPU 而不是 CPU 吗?

有一个古老的技巧可以解决这个问题:

.useGpu {
  transform: translateZ(0);
  will-change: transform;
}
Enter fullscreen mode Exit fullscreen mode

然后,在开发工具中,转到“图层”:
图层

看到“倒计时”现在有自己渲染层了吗?不确定现在是否还能用,不过加个也没什么坏处。


离开浏览器标签页

当我离开浏览器标签页并返回时,纯 CSS 时钟没有出现任何问题。也许我等待的时间还不够长!但如果您遇到任何问题,请使用此事件重新计算时钟的延迟:

document.addEventListener('visibilitychange', () => {
  if (!document.hidden) { ... }
})
Enter fullscreen mode Exit fullscreen mode

模拟时钟

作为奖励——这是我前段时间做的一个模拟时钟:


现在是时候(双关语)结束这篇文章了!

封面图片由 DALL·E 提供。

文章来源:https://dev.to/madsstoumann/clocks-countdowns-timing-in-css-and-javascript-554n
PREV
CSS 中的吉他和弦
NEXT
构建反应组件