使用 CSS 中 sin() 和 cos() 三角函数创建时钟
原文链接 Creating a Clock with the New CSS sin() and cos() Trigonometry Functions -- 作者 Mads Stoumann
本文为译文,采用意译方式
正文
CSS
三角函数在这!如果你使用最新版的 Firefox
或者 Safari
(当然 Chrome
也可以)浏览器,那就可以使用它们。在 CSS
中拥有这种数学能力,将开辟一大推可能性。在本教程中,我们尝试感受下这两个较新的函数:sin() 和 cos()。
还有其他的三角函数 -- 包括 tan()。为什么我们只关注 sin()
和 cos()
呢?因为这很符合我的想法,它们可以把文本放在圆圈的边缘。当 Chris 分享了一种使用 Sass mixin 方法的时候,在 CSS-Tricks
上做了介绍。那已经是六年前的事了,我们来给予前言的应用。
如下。当前仅支持最新版的 Firefox
或者 Safari
(当然 Chrome
也可以)浏览器。
jcode
所以,它并不完全是像单词形成一个圆形,而是我们将文本字符沿着圆形形成一个时钟面。下面是一些我们开始的标记。
<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>
接着,这里对 .clock-face
容器有些基本样式。我决定使用带有 datetime
属性的 <time>
标签。
.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 变量 这是宽度值的。我们不久后会使用到它:
1_art_experiment.webp它看起来有点现代艺术体验,是吧?我们来介绍下一个变量,--_r
,其存储圆形半径,也就是等于圆形宽度的一半。如果宽度(--_w
)改变,那么半径值(--_r
)也会更新 -- 这归功于另外一个 CSS
数学函数 calc()
:
.clock {
--_w: 300px;
--_r: calc(var(--_w) / 2);
/* rest of styles */
}
现在是一些数学计算。一个圆形是 360
度。我们的时钟有 12
个标记,所以我们每 30
度(360 / 12
)放置一个数字。在数学领域,一个圆形开始于 3
点钟方向,所以中午时分要减去 90
度,也就是 270
度(360 - 90
)。
我们添加另一个变量,--_d
,我们可以使用它在时钟表面为每个数字设置角度。我们将以每 30
度递增,以完善圆形:
.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; }
OK
,现在是时候让我们对 sin()
和 cos()
函数尝尝鲜了。我们使用它们获取每个数字的坐标信息,以便我们将它们放置在时钟面的正确位置。
X
坐标点的位置公式是 半径 + (半径 * cos(度数))
,我们将其存放在新的变量 --_x
中:
--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
Y
坐标点的位置公式是 半径 + (半径 * sin(度数))
,我们将其存放在新的变量 --_y
中:
--_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
,我们稍后会用于设置数字的宽度和高度。目前为止,我们得到的结果:
这绝对看起来像一个时钟!想想怎么将每个数字的 top-left
角环绕圆形放置在正确的位置?当计算每个数字位置,我们需要收缩半径。在计算半径之前,我们可以从圆形的宽度 --_w
中减去 --_sz
值:
--_r: calc((var(--_w) - var(--_sz)) / 2);
3_correct_pos.webp
好多了!我们更改下颜色,让它看起来更优雅:
nY6Vbx2b.webp我们可以止步于此了!我们实现了将文本环绕于圆圈的目标,是吧?但是,哪有没有时分秒指针的时钟呢?
我们使用简单的 CSS
动画实现它。首先,在 HTML
标记中我们多添加三个元素。
<div class="clock">
<!-- after <time>-tags -->
<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
属性只接受毫秒或者秒的值。我们以秒计算,那就是 43200
秒(60 秒 * 60 分 * 12 小时
)。
animation: turn 43200s infinite;
分针绕一圈需要一个小时。但是,我们希望是 multi-step animation。所以指针之间的移动是交错的,而不是线性的。我们将需要 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;
}
我们想以现在的时间开始怎么办?我们需要些许 JavaScript
辅助下:
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"
在钟表,然后设置两个新的自定义属性,设置负值 animation-delay
,正如 Mate Marschalko
在 when he shared a CSS-only clock 中所做那样。JavaScript
的时间对象中的 getHours()
方法是使用 24
小时格式,所以我们使用 remainder
operator 将其转换成 12
小时形式。
在 CSS
中,我们也需要添加 animation-delay
:
.minutes {
animation-delay: var(--_dm, 0s);
/* other styles */
}
.hours {
animation-delay: var(--_dh, 0s);
/* other styles */
}
还有一件事。使用 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)))
}
}
至此,我们的时钟已经完成了!再次放出最终的案例。目前,它仅支持最新版的 Firefox
或者 Safari
(当然 Chrome
也可以)浏览器。jcode
我们还可以做什么?
这里仅是个思想碰撞,我们可以时钟更改为环形图片展,通过将 <time>
标签替换成 <img>
标签,然后更新宽度 (--_w
) 和 半径 (--_r
)值:
jcode
我们再尝试一个。之前我就提及时钟看起来像现代艺术体验。我们可以利用它并重新创建前几天我在画廊的海报上看到的图案(不幸的是我当初没买)。现在回忆,它被称为 Moon
,是由一堆的点形成的圆形。
这次我们将使用无序列表,因为圆形并不遵循特定的顺序。我们不打算再一个标签中添加所有的列表项。而是通过 JavaScript
将其注入,并且添加一些控件以便我们操控最终的结果。
控件包括范围输入框(<input type="range">
),我们将其放置在 <form>
元素内,然后监听输入框事件。
<form id="controls">
<fieldset>
<label>Number of rings
<input type="range" min="2" max="12" value="10" id="rings" />
</label>
<label>Dots per ring
<input type="range" min="5" max="12" value="7" id="dots" />
</label>
<label>Spread
<input type="range" min="10" max="40" value="40" id="spread" />
</label>
</fieldset>
</form>
运行下面 input
中的方法,我们将创建一堆带有角度(--_d
)变量的 <li>
元素,那些角度像我们之前应用那样。我们也可以调整我们的半径变量(--_r
)。
我还想不同的点有不同的颜色。所以,我们为每个列表项随机生成(也不是完全随机) HSL
颜色,并将其存放在一个新的 CSS
变量,--_bgc
:
const update = () => {
let s = "";
for (let i = 1; i <= rings.valueAsNumber; i++) {
const r = spread.valueAsNumber * i;
const theta = coords(dots.valueAsNumber * i);
for (let j = 0; j < theta.length; j++) {
s += `<li style="--_d:${theta[j]};--_r:${r}px;--_bgc:hsl(${random(
50,
25
)},${random(90, 50)}%,${random(90, 60)}%)"></li>`;
}
}
app.innerHTML = s;
}
random()
函数根据给定的数字范围生成值:
const random = (max, min = 0, f = true) => f ? Math.floor(Math.random() * (max - min) + min) : Math.random() * max;
完成了。我们使用 JavaScript
来渲染标签,一旦渲染完成,我们就不需要它了。sin()
和 cos()
函数帮助我们将所有点定位到正确位置。jcode
总结
将东西环绕圆形放置是一个很基础的例子,用于展示三角函数,比如 sin()
和 cos()
的强大。但是,这真是太棒了,我们将获取现代 CSS
特性,为旧的解决方法提供新的的解决方案。我确信,我们在看到更加有趣、复杂和创造性的使用案例,特别是当 Chrome
和 Edge
对特性支持的时候。(截止目前,Chrome
最新版已经支持)。