使用 CSS 中 sin() 和 cos() 三角函数创建时钟

共 8011字,需浏览 17分钟

 ·

2023-08-19 02:08


原文链接 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 变量 这是宽度值的。我们不久后会使用到它:

d403078c754e13f64efae06eb616624e.webp1_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,我们稍后会用于设置数字的宽度和高度。目前为止,我们得到的结果:

71c10522a5b3251fbbbc0b429f3444c5.webp2_off_pos.webp

这绝对看起来像一个时钟!想想怎么将每个数字的 top-left 角环绕圆形放置在正确的位置?当计算每个数字位置,我们需要收缩半径。在计算半径之前,我们可以从圆形的宽度 --_w 中减去 --_sz 值:

      
      --_r: calc((var(--_w) - var(--_sz)) / 2);
fe831bd05727a67e881131134df93e0c.webp3_correct_pos.webp

好多了!我们更改下颜色,让它看起来更优雅:

a8bfa00d061c7983fa2d9dbcd4dc6051.webpnY6Vbx2b.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,是由一堆的点形成的圆形。

5228bd86e961557a62a14159efc8ff29.webps_2756772B6AAC4896CD9BFF036E07360F7C8D595A4B775A1B72DBF756AC4018A4_1675325303440_image.webp

这次我们将使用无序列表,因为圆形并不遵循特定的顺序。我们不打算再一个标签中添加所有的列表项。而是通过 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 特性,为旧的解决方法提供新的的解决方案。我确信,我们在看到更加有趣、复杂和创造性的使用案例,特别是当 ChromeEdge 对特性支持的时候。(截止目前,Chrome 最新版已经支持)。

3a4ed45c997d1c8283f2df037be1f29e.webpWX20230312-234540@2x.png


浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报