时钟和倒计时:CSS 和 JavaScript 中的计时
有一天我需要一个数字时钟组件,所以我很快编写了一个简单的 JavaScript 方法:
function uiDigitalClock(node) {
const now = () => {
node.textContent = new Date().toLocaleTimeString()
requestAnimationFrame(now)
}
now()
}
我在某处读到过,其requestAnimationFrame
性能应该比更好setInterval
,但这种方法一直困扰着我。
在最佳条件下,每秒requestAnimationFrame
将触发60 次- 这是数字时钟所需次数的 60 倍!
Date()
每秒创建 60 次对象对于性能来说肯定是不利的!
所以我尝试了setInterval
:
function interval(node){
return setInterval(() => node.textContent = new Date().toLocaleTimeString(), 1000)
}
同样,代码不多,视觉效果是一样的——每秒只有一个对象。 Date()
怎么样setTimeout()
?
function timeout(node) {
const now = () => {
node.textContent = new Date().toLocaleTimeString()
setTimeout(now, 1000)
}
now()
}
它也可以得到类似的结果,但有一个恼人的延迟(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);
}
在所有 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;
}
然后,在<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) ' '; }
}
要制作秒数动画,需要一个关键帧:
@keyframes seconds {
from { --seconds: 0;}
to { --seconds: 60; }
}
就分钟数而言,几乎相同,但动画耗时却长了 60 倍(60 x 60 = 3600):
animation: minutes 3600s steps(60, end) infinite;
对于小时数,我们需要将该数字乘以 24:
animation: hours 86400s steps(24, end) infinite;
耶!我们终于有了一个能用的 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);
...我们可以更新之前指定的 CSS 属性,例如:
node.style.setProperty(`--delay-seconds`, `${seconds}s`);
现在,我们有一个可以工作的数字 CSS 时钟——将它与此处的其他方法进行比较:
如果您检查开发工具中的标记,您会发现 CSS 版本没有重写 DOM 内容。
倒计时
在此之后,我决定重新审视我的旧 Codepen、多语言倒计时,并制作一个仅 CSS 版本:
locale
如果您想要使用其他语言,您可以在 JS 代码中尝试一下:
但是性能呢?CSS 可能不会像 JavaScript 那样阻塞主线程,但我们能确定它使用的是 GPU 而不是 CPU 吗?
有一个古老的技巧可以解决这个问题:
.useGpu {
transform: translateZ(0);
will-change: transform;
}
看到“倒计时”现在有自己的渲染层了吗?不确定现在是否还能用,不过加个也没什么坏处。
离开浏览器标签页
当我离开浏览器标签页并返回时,纯 CSS 时钟没有出现任何问题。也许我等待的时间还不够长!但如果您遇到任何问题,请使用此事件重新计算时钟的延迟:
document.addEventListener('visibilitychange', () => {
if (!document.hidden) { ... }
})
模拟时钟
作为奖励——这是我前段时间做的一个模拟时钟:
现在是时候(双关语)结束这篇文章了!
封面图片由 DALL·E 提供。
文章来源:https://dev.to/madsstoumann/clocks-countdowns-timing-in-css-and-javascript-554n