上一篇【setTimeout不准时,CSS精准实现计时器功能】的博文,最后提到了通过 CSS 动画实现计时器的方式。
本文详情描述如何通过 CSS 完整实现时钟效果,这也是团队 21 年专利的一项内容(专利公布号:CN114003087A)。
该发明专利主要解决大屏下通过 javascript 实现的时钟不准确的问题。通过 CSS 动画进行计时,避免同步阻塞卡顿的问题。
前置知识
伪元素
伪元素允许你对被选择元素的特定部分修改样式。
::after
用来创建一个伪元素,作为已选中元素的最后一个子元素。通常会配合content
属性来为该元素添加装饰内容。这个虚拟元素默认是行内元素。
伪元素:before
和:after
添加的内容默认是inline元素
- 伪元素不属于文档,所以js无法操作它
- 伪元素属于主元素的一部分,因此点击伪元素触发的是主元素的click事件
- 块级元素才能有:before, :after,其实是不妥的,大部分行级元素也可以设置伪元素,但是像img可替换元素,因为其外观和尺寸有外部资源决定,那么如果外部资源正确加载,就会替换掉其内部内容,这时伪元素也会被替换掉,但是当外部资源加载失败时,设置的伪元素是可以起作用的。
示例
a::after {
content: "→";
}
数据属性 data-*
HTML5 有扩展性的设计,它初衷是数据应与特定的元素相关联,但不需要任何定义。data-*
允许我们在标准内于 HTML 元素中存储额外的信息,而不需要使用类似于 classList 。
attr()
CSS 表达式 attr()
用来获取选择到的元素的某一 HTML 属性值,并用于其样式。它也可以用于伪元素,属性值采用伪元素所依附的元素。
attr()
理论上能用于所有的 CSS 属性但目前支持的仅有伪元素的 content 属性,其他的属性和高级特性目前是实验性的
示例
同 数据属性 结合,可以很好的实现相应的效果展示。
hover到 <a>
标签,展示对应 data-hover内容
<style>
a:hover::after {
content: attr(data-hover);
}
</style>
<body>
<a href="javascript:void(0);" data-hover="hover展示内容">hover</a>
</body>
css animation
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 |
steps(number_of_steps, direction)
:定义了一个阶梯函数,将输出值的域等距地划分。
示例
动画周期的时长 10s ,等距划分为10步,每秒执行一次 timer,无限次循环执行。
animation: timer 10s infinite steps(10) forwards;
translate
transform: translate(x, y)
/ translate: x y;
平移变换。
示例
a:hover {
translate: 200px 50px;
/*等价于*/
transform: translate(200px, 50px);
}
实现思路
步骤一:定义DOM
将时、分、秒进行DOM定义,针对时、分、秒将相关数据通过数据属性 data-*
进行绑定
<!-- 时 -->
<div class="card">
<div class="card-hours"> <!-- 见步骤四其作用 -->
<div class="hours" data-hours="18 19 20 21 22 23 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17"></div>
</div>
</div>
<div class="card">:</div>
<!-- 分 -->
<div class="card">
<div class="card-minutes"> <!-- 见步骤四其作用 -->
<div class="minutes"
data-minutes="58 59 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57">
</div>
</div>
</div>
<div class="card">:</div>
<!-- 秒 -->
<div class="card">
<div class="seconds"
data-seconds="50 51 52 53 54 55 56 57 58 59 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49">
</div>
</div>
关于data-hours
、data-minutes
、data-seconds
数据通过当前时间进行初始化(动态形式)。上述示例,初始时间为:18:58:50
上述,针对分钟、小时,多一层 DOM,<div class="card-hours">
、 <div class="card-minutes">
后续解释作用(见步骤四)。
步骤二:展示区域基础定义
这里针对:时、分、秒 统一设置。
设置一个固定大小的 font-size
,确保每行只能展示一个数字;同时,指定 line-height
,确保可视区内只有展示一个数字。(为后续动画做准备)
body {
font-size: 48px;
}
.card {
display: inline-block;
height: 68px;
overflow: hidden;
}
.hours, .minutes, .seconds {
width: 68px;
line-height: 68px;
}
font-size
、line-height
属性,具有继承性,贯穿整个网页font-size
、line-height
属性,和字体宽度没有必然的换算关系;如果希望字体是等宽的,需要使用等宽字体(如:Consolas、Monaco、monospace)
步骤三:实现「秒」
每一个周期60s,等距划分为60份(六十进制),每份1s,然后通过 translate
来展示下一个数值。
.seconds:after {
display: block;
content: attr(data-seconds);
animation: counter 60s steps(60) infinite forwards;
}
@keyframes counter {
from {
translate: 0 0;
}
to {
translate: 0 -100%;
}
}
步骤四:实现「分、时」
对于分钟、小时,滚动展示的逻辑同秒一致,只是切割的份数及单位时间需要根据实际的来。
- 分钟:等距 60 份(六十进制),一个周期 60 ∗ 60 = 3600 60 * 60 = 3600 60∗60=3600
- 小时:等距 24 份(二十四进制),一个周期 $ 24 * 60 * 60 = 86400$
.minutes:after {
display: block;
content: attr(data-minutes);
animation: counter 3600s steps(60) infinite forwards;
animation-delay: 10s; /* 延后执行 */
}
.hours:after {
display: block;
content: attr(data-hours);
animation: counter 86400s infinite steps(24) forwards;
animation-delay: 70s; /* 延后执行 */
}
分钟、小时与秒不同的是,第一次进制大概率不是整 60 或 24。
以上述实例解释:当前秒为 50,再过 10s(计算方式:$ 60 - 50 ),分钟就应该变成 59 ;同样的,再过 70 s (计算方式: ),分钟就应该变成 59;同样的,再过 70s(计算方式: ),分钟就应该变成59;同样的,再过70s(计算方式: 3600 - 58*60 - 50 $),小时就应该变成 19。
所以,上述 animation-delay
就是让固定周期的动画,稍后开始,等待的时间就是依据初始时间计算而来。
animation-delay
指定从应用动画到元素开始执行动画之前等待的时间量
解释清楚了延迟的作用,但问题是:首次的动画如何执行?(上述 animation
是从等待后的完整周期开始的)
步骤一,中提到的 <div class="card-hours">
、 <div class="card-minutes">
就是为此。
.card-minutes {
height: 136px;
overflow: hidden;
/* 60-pastSeconds */
animation: delay-counter 10s steps(1) 1 forwards;
}
.card-hours {
height: 136px;
overflow: hidden;
/* 3600-pastMinutes*60-pastSeconds */
animation: delay-counter 70s steps(1) 1 forwards;
}
@keyframes delay-counter {
from {
translate: 0 0;
}
to {
translate: 0 -50%;
}
}
上述动画只执行一次(初始化)
总结
CSS 动画运行效果良好,甚至在低性能的系统上。渲染引擎会使用跳帧或者其他技术以保证动画表现尽可能的流畅。
同时动画在各个执行阶段,也提供了相应的事件,这里暂不展开,有诉求的可以查看相关 MDN AnimationEvent。