CSS三角函数来了!好吧,如果您使用的是最新版本的 Firefox 和 Safari,那就是这样。CSS 中拥有这种数学能力开启了一大堆可能性。在本教程中,我们应该好好来感受一下几个新函数:sin()
和cos()
。
目前还有其他三角函数,比如tan()
,为什么只关注sin()
和cos()
? 那是因为它非常适合我想要实现的想法,就是沿着圆的边缘放置文本。在CSS-Tricks上,Chris分享了一种使用Sass mixin的方法来实现这一点。那是六年前的事情了,所以让我们来尝试一下最新的技术。
以下是简单的效果。再次强调,目前只在Firefox和Safari浏览器中支持:
点我查看例子
如何实现
所以,它并不完全像单词形成一个圆形,而是我们将字符沿着圆形排列,形成一个时钟的面板。下面我们就来使用html逐步分分析时钟结构:
<div class="clock">
<div class="clock-face">
<time datetime="12:00">12</time>
<time datetime="1:00">1</time>
<time datetime="2:00">2</time>
<time datetime="3:00">3</time>
<time datetime="4:00">4</time>
<time datetime="5:00">5</time>
<time datetime="6:00">6</time>
<time datetime="7:00">7</time>
<time datetime="8:00">8</time>
<time datetime="9:00">9</time>
<time datetime="10:00">10</time>
<time datetime="11:00">11</time>
</div>
</div>
这段CSS样式主要是用于给.clock-face
设置一个容器的基本属性和样式,记住嗷,要考的:
--_ow: clamp(5rem, 60vw, 40rem);
:这是一个CSS变量,用于设置容器的宽度。它使用了clamp
函数,其中第一个参数表示最小宽度(5rem),第二个参数表示视窗宽度的百分比(60vw),第三个参数表示最大宽度(40rem)。--_w: 88cqi;
:这是另一个CSS变量,用于设置容器的宽度。aspect-ratio: 1;
:设置容器的宽高比为1,即正圆形。background-color: tomato;
:设置容器的背景颜色为番茄红。border-radius: 50%;
:设置容器的圆角半径为50%,使其呈现圆形。container-type: inline;
:设置容器的类型为内联元素,即与其他元素在一行上显示。display: grid;
:将容器的显示模式设置为网格布局。height: var(--_ow);
:设置容器的高度为之前定义的变量--_ow
的值。place-content: center;
:将容器内容在网格布局中居中对齐。position: relative;
:设置容器的定位方式为相对定位。width: var(--_ow);
:设置容器的宽度为之前定义的变量--_ow
的值。
这些样式通过定义CSS变量和一些基本属性来创建一个圆形的容器,并使其在网格布局中居中显示。
.clock {
--_ow: clamp(5rem, 60vw, 40rem);
--_w: 88cqi;
aspect-ratio: 1;
background-color: tomato;
border-radius: 50%;
container-type: inline;
display: grid;
height: var(--_ow);
place-content: center;
position: relative;
width var(--_ow);
}
这一步呢,我们把轮廓画出来,给它稍微装饰下,以至于让我们知道大概思路。并且我们存入了css变量。
看起来像不像一个艺术品...好了,现在引入一个新的变量 --_r
,用于存储圆的半径,该半径等于圆的宽度的一半。这样,如果宽度 --_w
发生变化,半径值 --_r
也会更新,这要归功于另一个 CSS 数学函数 calc()
:
.clock {
--_w: 300px;
--_r: calc(var(--_w) / 2);
/* 其他样式 */
}
现在需要用到一些数学知识。一个圆有 360 度。时钟上有 12 个标签,所以我们希望每 30 度(360 / 12)放置一个数字。在数学上,圆开始于 3 点钟位置,所以正午实际上是从那个位置减去 90 度,即 270 度(360 - 90)。
让我们再添加一个变量 --_d
,可以用来为时钟面上的每个数字设置一个角度值。我们将递增这些值以完成整个圆:
.clock time:nth-child(1) {
--_d: 270deg; }
.clock time:nth-child(2) {
--_d: 300deg; }
.clock time:nth-child(3) {
--_d: 330deg; }
.clock time:nth-child(4) {
--_d: 0deg; }
.clock time:nth-child(5) {
--_d: 30deg; }
.clock time:nth-child(6) {
--_d: 60deg; }
.clock time:nth-child(7) {
--_d: 90deg; }
.clock time:nth-child(8) {
--_d: 120deg; }
.clock time:nth-child(9) {
--_d: 150deg; }
.clock time:nth-child(10) {
--_d: 180deg; }
.clock time:nth-child(11) {
--_d: 210deg; }
.clock time:nth-child(12) {
--_d: 240deg; }
通过以上代码,我们为时钟的每个数字设置了相应的角度值。这些角度值按照每次递增 30 度来完成整个圆的布局。
好的,现在是时候动手使用 sin()
和 cos()
函数了!我们想要使用它们来获取每个数字的 X 和 Y 坐标,以便将它们正确地放置在时钟面上。
X 坐标的公式是 radius + (radius * cos(degree))
。让我们将其插入到我们的新变量 --_x
中:
--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
Y 坐标的公式是radius + (radius * sin(degree))
。我们已经掌握了计算它所需的内容:
--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
还有一些基础设置,让我们对数字进行一些基本样式设置,以确保它们绝对定位并根据我们的坐标放置:
.clock-face time {
--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
--_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
--_sz: 12cqi;
display: grid;
height: var(--_sz);
left: var(--_x);
place-content: center;
position: absolute;
top: var(--_y);
width: var(--_sz);
}
注意到 --_sz
这个变量,我们将在稍后用它来设置数字的宽度和高度。让我们看看目前为止我们得到了什么结果。
不错...这看起来更像是一个时钟了,大家要注意每个数字的左上角都正确地位于圆周上的相应位置。在计算每个数字的位置时,我们需要“缩小”半径。在计算半径之前,我们可以从圆的大小(--_w)中减去数字的大小(--_sz):
--_r: calc((var(--_w) - var(--_sz)) / 2);
这样,我们可以将数字的大小考虑在内,确保它们在正确的位置上放置。继续下一步操作~
想要达到这种效果,我们需要给它优化下外观就可以了。
我们已经实现了在圆圈周围放置文本的目标,对吧?但是没有指针来显示小时、分钟和秒钟的时钟算什么呢?
使用一个CSS动画来完成。首先,在我们的标记中添加三个额外的元素:
<div class="clock">
<!-- 在 <time> 标签之后 -->
<span class="arm seconds"></span>
<span class="arm minutes"></span>
<span class="arm hours"></span>
<span class="arm center"></span>
</div>
然后为所有三个指针添加一些常见的标记。同样,大部分内容只是确保指针被绝对定位并相应地放置:
.arm {
background-color: var(--_abg);
border-radius: calc(var(--_aw) * 2);
display: block;
height: var(--_ah);
left: calc((var(--_w) - var(--_aw)) / 2);
position: absolute;
top: calc((var(--_w) / 2) - var(--_ah));
transform: rotate(0deg);
transform-origin: bottom;
width: var(--_aw);
}
我们将为所有三个指针使用相同的动画:
@keyframes turn {
to {
transform: rotate(1turn);
}
}
唯一的区别在于各个指针完成一次完整旋转所需的时间。对于小时指针,需要12小时完成一次完整旋转。animation-duration
属性只接受毫秒和秒的值。让我们选择秒,也就是43,200秒(60秒 60分钟 12小时)。
animation: turn 43200s infinite;
分钟指针需要1小时完成一次完整旋转。但是我们希望这是一个多步动画,所以指针之间的移动是交错的,而不是线性的。我们需要60个步骤,对应每一分钟:
animation: turn 3600s steps(60, end) infinite;
秒钟指针与分钟指针几乎相同,只是持续时间为60秒而不是60分钟:
animation: turn 60s steps(60, end) infinite;
然后更新一下我们在公共样式中创建的属性:
.seconds {
--_abg: hsl(0, 5%, 40%);
--_ah: 145px;
--_aw: 2px;
animation: turn 60s steps(60, end) infinite;
}
.minutes {
--_abg: #333;
--_ah: 145px;
--_aw: 6px;
animation: turn 3600s steps(60, end) infinite;
}
.hours {
--_abg: #333;
--_ah: 110px;
--_aw: 6px;
animation: turn 43200s linear infinite;
}
如果我们想要从当前时间开始呢?可以用过JS来实现:
const time = new Date();
const hour = -3600 * (time.getHours() % 12);
const mins = -60 * time.getMinutes();
app.style.setProperty('--_dm', `${
mins}s`);
app.style.setProperty('--_dh', `${
(hour+mins)}s`);
我在时钟面上添加了 id="app"
,并在其上设置了两个新的自定义属性,用于设置负数的动画延迟,就像 Mate Marschalko 在共享纯CSS时钟时所做的那样。JavaScript 的 Date 对象的 getHours()
方法使用的是24小时制,因此我们使用余数运算符将其转换为12小时制。
在CSS中,我们还需要添加 animation-delay
:
.minutes {
animation-delay: var(--_dm, 0s);
/* 其他样式 */
}
.hours {
animation-delay: var(--_dh, 0s);
/* 其他样式 */
}
只剩下最后一件事。使用 CSS 的 @supports 和我们已经创建的属性,我们可以为不支持 sin() 和 cos() 的浏览器提供回退方案。(感谢 Temani Afif!)
@supports not (left: calc(1px * cos(45deg))) {
time {
left: 50% !important;
top: 50% !important;
transform: translate(-50%,-50%) rotate(var(--_d)) translate(var(--_r)) rotate(calc(-1*var(--_d)))
}
}
图片库
好了,你不会以为我们时钟只能拿来当钟用吧???当然不是!!!
可以通过替换标签 <time>
然后 <img>
更新宽度 ( --_w
) 和半径 ( --_r
) 值来快速将时钟变成圆形图像库。