使用css的sin()和cos()方法创建时钟
需求
我们的目标是使用css的三角学方法sin
和cos
做出下图的时钟
要求:和真实的时钟效果一模一样
接下来,让我们逐步分析,如何实现真实时钟效果。
css还有其他的三角学方法,比如tan
,为何我仅仅只关注sin
和cos
呢?因为在我的心里,他们能完美地实现该功能。目前这个功能,只有现代浏览器支持良好,比如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
数学函数来实现该功能
现在页面的显示效果如下图所示
我们知道圆是360度,现在圆内部,我们有12个时间标记,现在我们想将这些时间标记每隔30(360/12)度放置。在数学世界里,一个圆开始于3点钟,所以呢12点应该在此基础上,减90度,所以12点是270度。
现在让我们新增另一个变量--_d
,我们可以用它为每个圆面数字设置度数值,我们将按照30度递增该变量值,来完成我们的圆。
好,现在轮到sin()
和cos()
两个函数出马啦!我们需要使用它们计算12个数字的x
和y
坐标值。
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);
}
页面样式如下图所示
看起来很像一个时钟啦!但是右下角的数字貌似偏移了,该如何解决呢?我们在计算每个数字的位置时,需要压缩下圆的半径
--_r: calc((var(--_w) - var(--_sz)) / 2);
我们需要定变量--_r
重新定义在.clock-face time
内,否则无法获取到--_sz
变量值
现在页面运行效果如下所示
现在让我们修改整体样式,让它看起来像下图
完整代码如下所示
<!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);
}
呼啦!我们的时钟效果完成啦,见下动图
完整代码如下所示
<!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)))
}
}
最终代码
我们仍然需要对样式细节进一步美化,达到最终时钟效果
<!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>
转载自:https://juejin.cn/post/7351662722934767631