likes
comments
collection
share

使用css的sin()和cos()方法创建时钟

作者站长头像
站长
· 阅读数 13

需求

我们的目标是使用css的三角学方法sincos做出下图的时钟

使用css的sin()和cos()方法创建时钟

要求:和真实的时钟效果一模一样

接下来,让我们逐步分析,如何实现真实时钟效果。

css还有其他的三角学方法,比如tan,为何我仅仅只关注sincos呢?因为在我的心里,他们能完美地实现该功能。目前这个功能,只有现代浏览器支持良好,比如Firefox、Safari、Chrome。

正文

使用HTML标签,搭建时钟圆脸

我们将1、2、3等数字沿着时钟圆脸路径放置,下面是代码结构

<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容器的样式如下所示

.clock {
  --_w: 300px;
  --_r: calc(var(--_w) / 2);
  aspect-ratio: 1;
  background-color: tomato;
  border-radius: 50%;
  container-type: inline-size;
  display: grid;
  height: var(--_w);
  place-content: center;
  position: relative;
  width: var(--_w);
}

--_r变量,存储circle的半径,它的值为circle宽度的一半,当宽度--_w变化时,这个--_r值也会随之变化,所以我们使用calc数学函数来实现该功能

现在页面的显示效果如下图所示

使用css的sin()和cos()方法创建时钟

我们知道圆是360度,现在圆内部,我们有12个时间标记,现在我们想将这些时间标记每隔30(360/12)度放置。在数学世界里,一个圆开始于3点钟,所以呢12点应该在此基础上,减90度,所以12点是270度。

现在让我们新增另一个变量--_d,我们可以用它为每个圆面数字设置度数值,我们将按照30度递增该变量值,来完成我们的圆。

好,现在轮到sin()cos()两个函数出马啦!我们需要使用它们计算12个数字的xy坐标值。

x坐标值套用计算公式radius + (radius * cos(degree)),现在让我们新增--_x变量

--_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));

y坐标值套用计算公式radius + (radius * sin(degree)),新增的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;
  background: green;
  top: var(--_y);
  width: var(--_sz);
}

页面样式如下图所示

使用css的sin()和cos()方法创建时钟

看起来很像一个时钟啦!但是右下角的数字貌似偏移了,该如何解决呢?我们在计算每个数字的位置时,需要压缩下圆的半径

--_r: calc((var(--_w) - var(--_sz)) / 2);

我们需要定变量--_r重新定义在.clock-face time内,否则无法获取到--_sz变量值

现在页面运行效果如下所示

使用css的sin()和cos()方法创建时钟

现在让我们修改整体样式,让它看起来像下图

使用css的sin()和cos()方法创建时钟

完整代码如下所示

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Clock</title>
    <style>
      .clock {
        --_ow: clamp(5rem, 60vw, 40rem);
        --_w: 88cqi;
        --_r: calc((var(--_w) - var(--_sz)) / 2);
        --_sz: 12cqi;

        background: #222;
        block-size: var(--_ow);
        border-radius: 24%;
        container-type: inline-size;
        display: grid;
        font-family: ui-sans-serif, system-ui, sans-serif;
        inline-size: var(--_ow);
        margin-inline: auto;
        place-content: center;
      }

      .clock-face {
        aspect-ratio: 1;
        background: var(--_bgc, #fff);
        border-radius: 50%;
        block-size: var(--_w);
        font-size: 6cqi;
        font-weight: 700;
        list-style-type: none;
        inline-size: var(--_w);
        padding: unset;
        position: relative;
      }

      .clock-face time {
        --_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
        --_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
        display: grid;
        height: var(--_sz);
        left: var(--_x);
        place-content: center;
        position: absolute;
        top: var(--_y);
        width: var(--_sz);
      }

      .clock-face time:nth-child(1) {
        --_d: 270deg;
      }
      .clock-face time:nth-child(2) {
        --_d: 300deg;
      }
      .clock-face time:nth-child(3) {
        --_d: 330deg;
      }
      .clock-face time:nth-child(4) {
        --_d: 0deg;
      }
      .clock-face time:nth-child(5) {
        --_d: 30deg;
      }
      .clock-face time:nth-child(6) {
        --_d: 60deg;
      }
      .clock-face time:nth-child(7) {
        --_d: 90deg;
      }
      .clock-face time:nth-child(8) {
        --_d: 120deg;
      }
      .clock-face time:nth-child(9) {
        --_d: 150deg;
      }
      .clock-face time:nth-child(10) {
        --_d: 180deg;
      }
      .clock-face time:nth-child(11) {
        --_d: 210deg;
      }
      .clock-face time:nth-child(12) {
        --_d: 240deg;
      }

      html {
        display: grid;
        height: 100%;
      }
      body {
        background-image: linear-gradient(
          175deg,
          rgb(128, 202, 190),
          rgb(85, 170, 160),
          rgb(60, 139, 139)
        );
        padding-block-start: 2em;
      }
      p {
        display: none;
        font-family: ui-sans-serif, system-ui, sans-serif;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <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>
  </body>
</html>

上面涉及到两个知识小点:clamp()函数、cqi尺寸单位

clamp

css3的clamp()函数的作用是把一个值限制在一个上限和下限之间,当这个值超出最小值或最大值的范围时,便在最小值和最大值之间选择一个值使用。

其使用语法如下所示

div {
    font-size: clamp(20px, 18px, 40px); 
    width: clamp(100px, 100%, 200px);
}

clamp()函数内部的三个参数分别表示:最小值、首选值、最大值

  • 当首选值小于最小值,则使用最小值
  • 当首选值介于最小值和最大值之间时,用首选值
  • 当首选值比最大值要大时,则使用最大值

cqi

cqi表示容器的内联尺寸占比,1cqi等于容器内联尺寸的1%,默认情况下等于cqw cqw表示容器的查询宽度占比,1cqw等于容器宽度的1%,假设容器宽度是1000px,则此时1cqw对应10px

来,我们看一段英文描述

  • cqw: 1% of a query container's width
  • cqi: 1% of a container's inline size

当容器内联尺寸变化时,我们的.clock宽度也随之发生变化。

添加时、分、秒指针

添加时、分、秒指针标签

<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的样式

.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);
}

我们对三个.arm应用同样的动画效果

@keyframes turn {
  to {
    transform: rotate(1turn);
  }
}

只不过时、分、秒每转一圈消耗的时间不同,比如时针转一圈,消耗12小时,让我们以秒为单位来计算,共计43,200 seconds (60 seconds * 60 minutes * 12 hours)

animation: turn 43200s infinite;

分针需要1个小时转一圈,但我们希望当分针在不同的.arm间移动时,它的动画效果是多个阶段性,而非线性动画。我们需要共计60步,每一步代表1分钟

animation: turn 3600s steps(60, end) infinite;

秒针的动画效果和分针差不多,但是转动一圈的耗时是60s

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 app = document.getElementById("app");
    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`);

我们给.clock-face添加id属性值app,并新增两个属性--_dm--_dh。由于getHours方法在JavaScript时间对象中使用的是24小时制,所以我们需要使用remainder operator将其转变为12小时制

在css里,我们需要添加animation-delay

     .minutes {
        --_abg: #333;
        --_ah: 145px;
        --_aw: 6px;
        animation: turn 3600s steps(60, end) infinite;
        animation-delay: var(--_dm, 0s);
      }
      .hours {
        --_abg: #333;
        --_ah: 110px;
        --_aw: 6px;
        animation: turn 43200s linear infinite;
        animation-delay: var(--_dh, 0s);
      }

呼啦!我们的时钟效果完成啦,见下动图

使用css的sin()和cos()方法创建时钟

完整代码如下所示

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Clock</title>
    <style>
      .clock {
        --_ow: clamp(5rem, 60vw, 40rem);
        --_w: 88cqi;
        --_r: calc((var(--_w) - var(--_sz)) / 2);
        --_sz: 12cqi;

        background: #222;
        block-size: var(--_ow);
        border-radius: 24%;
        container-type: inline-size;
        display: grid;
        font-family: ui-sans-serif, system-ui, sans-serif;
        inline-size: var(--_ow);
        margin-inline: auto;
        place-content: center;
      }

      .clock-face {
        aspect-ratio: 1;
        background: var(--_bgc, #fff);
        border-radius: 50%;
        block-size: var(--_w);
        font-size: 6cqi;
        font-weight: 700;
        list-style-type: none;
        inline-size: var(--_w);
        padding: unset;
        position: relative;
      }

      .clock-face time {
        --_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
        --_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
        display: grid;
        height: var(--_sz);
        left: var(--_x);
        place-content: center;
        position: absolute;
        top: var(--_y);
        width: var(--_sz);
      }

      .clock-face time:nth-child(1) {
        --_d: 270deg;
      }
      .clock-face time:nth-child(2) {
        --_d: 300deg;
      }
      .clock-face time:nth-child(3) {
        --_d: 330deg;
      }
      .clock-face time:nth-child(4) {
        --_d: 0deg;
      }
      .clock-face time:nth-child(5) {
        --_d: 30deg;
      }
      .clock-face time:nth-child(6) {
        --_d: 60deg;
      }
      .clock-face time:nth-child(7) {
        --_d: 90deg;
      }
      .clock-face time:nth-child(8) {
        --_d: 120deg;
      }
      .clock-face time:nth-child(9) {
        --_d: 150deg;
      }
      .clock-face time:nth-child(10) {
        --_d: 180deg;
      }
      .clock-face time:nth-child(11) {
        --_d: 210deg;
      }
      .clock-face time:nth-child(12) {
        --_d: 240deg;
      }
      .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);
      }
      .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;
        animation-delay: var(--_dm, 0s);
      }
      .hours {
        --_abg: #333;
        --_ah: 110px;
        --_aw: 6px;
        animation: turn 43200s linear infinite;
        animation-delay: var(--_dh, 0s);
      }
      @keyframes turn {
        to {
          transform: rotate(1turn);
        }
      }
      html {
        display: grid;
        height: 100%;
      }
      body {
        background-image: linear-gradient(
          175deg,
          rgb(128, 202, 190),
          rgb(85, 170, 160),
          rgb(60, 139, 139)
        );
        padding-block-start: 2em;
      }
      p {
        display: none;
        font-family: ui-sans-serif, system-ui, sans-serif;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="clock">
      <div class="clock-face" id="app">
        <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>
        <span class="arm seconds"></span>
        <span class="arm minutes"></span>
        <span class="arm hours"></span>
        <span class="arm center"></span>
      </div>
    </div>
  </body>
  <script>
    const app = document.getElementById("app");
    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`);
  </script>
</html>

浏览器兼容

我们可以使用CSS的@supports方法,兼容不支持sin()cos()方法的浏览器,如下代码所示

@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)))
  }
}

最终代码

我们仍然需要对样式细节进一步美化,达到最终时钟效果

使用css的sin()和cos()方法创建时钟

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Clock</title>
    <style>
      .clock {
        --_ow: clamp(5rem, 60vw, 40rem);
        --_w: 88cqi;
        --_r: calc((var(--_w) - var(--_sz)) / 2);
        --_sz: 12cqi;

        background: #222;
        block-size: var(--_ow);
        border-radius: 24%;
        container-type: inline-size;
        display: grid;
        font-family: ui-sans-serif, system-ui, sans-serif;
        inline-size: var(--_ow);
        margin-inline: auto;
        place-content: center;
      }

      .clock-face {
        aspect-ratio: 1;
        background: var(--_bgc, #fff);
        border-radius: 50%;
        block-size: var(--_w);
        font-size: 6cqi;
        font-weight: 700;
        list-style-type: none;
        inline-size: var(--_w);
        padding: unset;
        position: relative;
      }

      .clock-face time {
        --_x: calc(var(--_r) + (var(--_r) * cos(var(--_d))));
        --_y: calc(var(--_r) + (var(--_r) * sin(var(--_d))));
        display: grid;
        height: var(--_sz);
        left: var(--_x);
        place-content: center;
        position: absolute;
        top: var(--_y);
        width: var(--_sz);
      }

      .clock-face time:nth-child(1) {
        --_d: 270deg;
      }
      .clock-face time:nth-child(2) {
        --_d: 300deg;
      }
      .clock-face time:nth-child(3) {
        --_d: 330deg;
      }
      .clock-face time:nth-child(4) {
        --_d: 0deg;
      }
      .clock-face time:nth-child(5) {
        --_d: 30deg;
      }
      .clock-face time:nth-child(6) {
        --_d: 60deg;
      }
      .clock-face time:nth-child(7) {
        --_d: 90deg;
      }
      .clock-face time:nth-child(8) {
        --_d: 120deg;
      }
      .clock-face time:nth-child(9) {
        --_d: 150deg;
      }
      .clock-face time:nth-child(10) {
        --_d: 180deg;
      }
      .clock-face time:nth-child(11) {
        --_d: 210deg;
      }
      .clock-face time:nth-child(12) {
        --_d: 240deg;
      }

      .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);
      }
      .seconds {
        --_abg: rgb(255, 140, 5);
        --_ah: 40cqi;
        --_aw: 1cqi;
        animation: turn 60s linear infinite;
        animation-delay: var(--_ds, 0ms);
      }

      .minutes {
        --_abg: #333;
        --_ah: 35cqi;
        --_aw: 2.5cqi;
        animation: turn 3600s steps(60, end) infinite;
        animation-delay: var(--_dm, 0ms);
      }

      .hours {
        --_abg: #333;
        --_ah: 30cqi;
        --_aw: 2.5cqi;
        animation: turn 43200s linear infinite; /* 60 * 60 * 12 */
        animation-delay: var(--_dh, 0ms);
        position: relative;
      }

      .hours::before {
        background-color: #fff;
        border: 1cqi solid #333;
        border-radius: 50%;
        content: "";
        display: block;
        height: 4cqi;
        position: absolute;
        bottom: -3cqi;
        left: -1.75cqi;
        width: 4cqi;
      }

      html {
        display: grid;
        height: 100%;
      }
      body {
        background-image: linear-gradient(
          175deg,
          rgb(128, 202, 190),
          rgb(85, 170, 160),
          rgb(60, 139, 139)
        );
        padding-block-start: 2em;
      }
      p {
        display: none;
        font-family: ui-sans-serif, system-ui, sans-serif;
        text-align: center;
      }

      @keyframes turn {
        to {
          transform: rotate(1turn);
        }
      }

      @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)));
        }
        p {
          display: block;
        }
      }
    </style>
  </head>
  <body>
    <p>
      <small>
        CSS sin() and cos() does <strong>NOT</strong> work in your browser.
      </small>
    </p>
    <div class="clock">
      <div class="clock-face" id="app">
        <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>
        <span class="arm seconds"></span>
        <span class="arm minutes"></span>
        <span class="arm hours"></span>
      </div>
    </div>
  </body>
  <script>
    /* to set current time */
    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`);
  </script>
</html>