实际开发过程中,我们会经常遇到,首次进入页面进行相应提示,然后指定时间后自动消失或者前端时钟展示等需求。
按照传统方案,我们可以使用 setTimeout
实现。但其存在:实际延时比设定值更久的情况。
setTimeout 不准时
有很多因素会导致 setTimeout 的回调函数执行比设定的预期值更久。
最小延时 >= 4ms
- If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4. – HTML5 spec Timers
在浏览器中,由于函数嵌套到一定深度,会导致被阻塞。
function cb() {
setTimeout(cb, 0)
}
setTimeout(cb, 0)
未被激活的 tabs 的定时最小延迟>=1000ms
为了优化 tab 的加载损耗(以及降低耗电量),在未被激活的 tab 中定时器的最小延时限制为 1S(1000ms)。
超时延迟
除了"最小延时"之外,定时器仍然有可能因为当前页面(或者操作系统/浏览器本身)被其他任务占用导致延时。
需要被强调是,直到调用 setTimeout()
的主线程执行完其他任务之后,回调函数和代码段才能被执行。
最大延时值
包括 IE、Chrome、Safari、Firefox 在内的浏览器其内部以 32 位带符号整数存储延时。这就会导致如果一个延时 (delay) 大于 2147483647 毫秒 (大约 24.8 天) 时就会溢出,导致定时器将会被立即执行。-- setTimeout/setInterval delay数值过大问题
打破 4ms 的限制
如果想在浏览器中实现 0ms 延时的定时器,可以尝试下述方法
(function() {
var timeouts = [];
var messageName = "zero-timeout-message";
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, "*");
}
function handleMessage(event) {
if (event.source == window && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
}
window.addEventListener("message", handleMessage, true);
window.setZeroTimeout = setZeroTimeout;
})();
CSS更准时
如果要实现特别准时的业务场景(如,时钟)。显然 setTimeout/setInterval 不是最佳实践,如何避免同步阻塞卡顿,是突破该问题的重要一点。
这里,使用 css 动画来实现,css 动画有几个显著的优点:
- 不依赖 javascript,且有成熟的相关 api;
- 运行效果良好,甚至在低性能的系统上。渲染引擎会使用跳帧或者其他技术以保证动画表现尽可能的流畅;
- 让浏览器控制动画序列,允许浏览器优化性能和效果,如降低位于隐藏选项卡中的动画更新频率。
animation 属性是 animation-name,animation-duration, animation-timing-function,animation-delay,animation-iteration-count,animation-direction,animation-fill-mode 和 animation-play-state 属性的一个简写属性形式。
属性 | 说明 | 示例 |
---|---|---|
animation-name | 指定应用的一系列动画 | animation1,animation2 |
animation-duration | 指定一个动画周期的时长,单位 s 或者 ms | 60s |
animation-timing-function | 在每一动画周期中执行的节奏 | ease 、linear 、steps(60) |
animation-delay | 定义动画于何时开始,单位 s 或者 ms | 100ms |
animation-iteration-count | 定义动画在结束前运行的次数 | infinite (无限次)、3 |
animation-direction | 指示动画是否反向播放 | normal 、alternate 、reverse |
animation-fill-mode | 设置 CSS 动画在执行之前和之后如何将样式应用于其目标 | forwards 、backwards |
animation-play-state | 定义一个动画是否运行或者暂停 | running 、paused |
animation: timer 60s infinite steps(60) forwards;
steps(number_of_steps, direction)
:定义了一个阶梯函数,将输出值的域等距地划分。
通过 自定义数据属性 绑定要显示的值。这样在 css 中,可以通过表达式 attr()
用来获取值。
<div class="container">
<p data-seconds="00 01 02 03 04 05 06 07 08 09"></p>
</div>
通过伪元素显示当前值(每次只显示1个),然后动画周期的时长 10s ,等距划分为10步。
每 1s 中移动向量的纵坐标。
.container p {
height: 68px;
width: 68px;
line-height: 68px;
overflow: hidden;
}
.container p::after {
display: block;
content: attr(data-seconds);
animation: timer 10s infinite steps(10) forwards;
}
@keyframes timer {
from {
transform: translate3d(0, 0, 0);
}
to {
transform: translate3d(0, -100%, 0);
}
}