写个时钟(进阶篇)

“写个时钟(行为篇)”中,我们通过 JavaScript 动态创建的时针、分针和秒针,并直接在 JavaScript 中通过控制行内样式的 transform 属性,设置 rotate 的值,实现指针的旋转。这样的方式,对于 DOM 的控制消耗较大,若放到大项目中,对项目性能具有一定的影响,也让 JavaScript 代码较多,降低了可读性

而且,设置实时时间的 setRealTime 函数,我们使用了递归调用,每隔一秒钟调用一次,那么每隔一秒钟就会运行整个函数一次,感觉简直是资源的浪费。

仔细想想,都已经是 4202 年了,感觉还是使用 JavaScript 大量控制 DOM 的方式,怎么看都有点点过时了。

曾经,听一位前端大神(尊重隐私,不透露名字)说过一句话:

 那么,秉承这一原则,我们对时钟页面作更加前卫的进阶处理吧!


一、时钟指针静态化

咱们前面是使用 JavaScript 动态生成的三个指针元素,通过 createElement 方法创建 DOM 对性能消耗较大,而且感觉不是必须的。于是我们将其直接写到 html 中:

<div class="dial">
  <div class="pointer hour"></div>
  <div class="pointer minute"></div>
  <div class="pointer second"></div>
</div>

如此一来,原本 JavaScript 中占有 16 行的创建指针的 createPointer 函数就可以删除了。

创建刻度的 createScale 函数不能删除,因为刻度有足足 60 个之多,直接在 html 中写 60 个 span 标签,还要每 5 个就给其加一个值为 bold 的 class 属性。相比之下,感觉还是在 JavaScript 中使用循环生成,代码显得更加简洁一些,逻辑上也更加优雅一些。

删除了创建指针的 createPointer 函数,对应的创建刻度的 createScale 函数的最后一行,就不能再调用创建指针的 createPointer 函数,而是应该直接调用设置实时时间的 setRealTime 函数:

/**
 * @description 创建刻度
*/
const createScale = () => {
  const oDiv = document.querySelector('.dial'); // 获取钟表盘面
  for (let i = 0; i < 60; i++) {
    const oSpan = document.createElement('span'); // 创建刻度元素
    oSpan.style.transform = `rotate(${6 * i}deg)`; // 设置旋转刻度属性
    if (i % 5 === 0) {
      oSpan.className = 'bold'; // 每 5 个刻度,让其显示突出一些
    }
    oDiv.appendChild(oSpan); // 将刻度放到钟表盘面
  }
  setRealTime(); // 设置实时时间
}

注意,设置实时时间的 setRealTime 函数,之前我们是把三个指针元素当作参数传入的,现在不再需要传入参数了。


二、重构设置实时时间函数

还是和“行为篇”一样的思路先构建一个设置实时时间的 setRealTime 函数,并获取到会用到的小时分钟

/**
 * @description 设置实时时间
*/
const setRealTime = () => {
  const now = new Date(); // 获取当前时间
  const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
}

我们知道,在 CSS 中,控制一个元素的旋转,都是通过 transform 属性中的 rotate 方法实现的,其语法为:transform: rotate(αdeg); 。

α 为旋转的度数,单位 deg 代表的是角度制

说白了,就是以 transform-origin 定义的坐标位置极坐标原点(未定义该属性,默认为该元素的几何中心,即:transform-origin: 50% 50% 0;),顺时针旋转 α 个度数。

既然拿到了实时时间中的小时分钟,那么我们就可以根据性质,让其转行成度数,并设置到元素中。 

document.documentElement.style.setProperty('--hour', `${30 * hour}deg`); // 根据当前小时设置小时指针的旋转角度
document.documentElement.style.setProperty('--minute', `${6 * minute}deg`); // 根据当前分钟设置分钟指针的旋转角度
document.documentElement.style.setProperty('--second', `${6 * second}deg`); // 根据当前秒设置秒指针的旋转角度

耶???这是嘛呀???

没错,考虑到我们是把三个指针元素直接写在 html 中的,从心理上来说,我是不愿意再次从 JavaScript 中获取这三个元素的。既然如此,就只好把在 JavaScript 中计算出来的传到 CSS 中进行处理。于是,CSS4 中的变量登场了:

.dial > .hour {
  top: 125px;
  width: 10px;
  height: 200px;
  transform-origin: center 175px;
  transform: rotate(var(--hour)) ;
}
.dial > .minute {
  top: 25px;
  width: 7.5px;
  height: 300px;
  transform-origin: center 275px;
  transform: rotate(var(--minute));
}
.dial > .second {
  top: 25px;
  width: 2.5px;
  height: 300px;
  transform-origin: center 275px;
  transform: rotate(var(--second));
}

对以上的 JavaScript 代码和 CSS 代码进行观察,相信聪明的您已经发现,我们在 JavaScript 中,通过 document.documentElement.style.setProperty 方法,给 CSS 声明了 --hour--minute--second 三个变量,其值为计算之后对应的角度值,还带有单位 deg。然后在 CSS 中对应的地方使用 var() 方法并传入对应的变量名,就可以取到相应的值

 

这样,我们就获取了一个当前时间显示的效果。大功告成了……………………吗? 

仔细观察就会发现,图中的当前时间是 10:46:11。在现实中,这个时候的时针和分针都不应该正正的指向对应值的刻度,而是会顺时针方向向前偏移对应的比例。 

理想是美好的,现实问题也是赤裸裸的存在的:这样的理想情况,怎么实现呢?

首先,我们单独用一个变量 sStart 记录秒针当前度数(也叫起始位置):

const sStart = 6 * second; // 设置秒指针的起始位置

分钟有 60 个刻度,那么 sStart / 60 就是分针应该在顺时针方向多偏移角度。于是我们声明一个记录分针当前度数的变量 mStart

const mStart = 6 * minute + sStart / 60; // 设置分钟指针的起始位置

同样的道理,小时有 12 刻度,那么 mStart / 12 就是时针应该在顺时针方向多偏移角度。于是我们也声明一个记录时针当前度数的变量 hStart

const hStart = 30 * hour + mStart / 12; // 设置小时指针的起始位置

于是,整个设置实时时间的 setRealTime 函数就变成了这样:

/**
 * @description 设置实时时间
*/
const setRealTime = () => {
  const now = new Date(); // 获取当前时间
  const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
  const sStart = 6 * second; // 设置秒指针的起始位置
  const mStart = 6 * minute + sStart / 60; // 设置分钟指针的起始位置
  const hStart = 30 * hour + mStart / 12; // 设置小时指针的起始位置

  document.documentElement.style.setProperty('--hour', `${hStart}deg`); // 根据当前小时设置小时指针的旋转角度
  document.documentElement.style.setProperty('--minute', `${mStart}deg`); // 根据当前分钟设置分钟指针的旋转角度
  document.documentElement.style.setProperty('--second', `${sStart}deg`); // 根据当前秒设置秒指针的旋转角度
}

得到的效果如下:

 

嗯~ o(* ̄▽ ̄*)o~~~~~~无论如何,看时针和分针,确实和现实中的钟表显示一致了。 


三、让时钟动起来

前面“行为篇”中,我们是采用在设置实时时间的 setRealTime 函数中进行递归调用的。尽管我们只需要每隔一秒钟调用一次该函数,但是每次调用都会把上面繁琐的计算都运行一次,资源浪费不说,总是过不了心里的这一关。

设想一下,咱们已经根据当前时间让指针的起始位置都确定了。接下来只需要设置让时针自动每 12 个小时(216000 秒)绕一圈,让分针自动每一个小时(3600 秒)绕一圈,让秒针自动每 60 秒绕一圈,岂不美哉? 

三个指针都是绕一圈继续重复,需要具备两个条件:

  1. 需要知道每一个指针绕一圈之后的度数,即:起始位置 + 360 deg

  2. 需要使用 CSS 动画来完成。

那么,我们先在设置实时时间的 setRealTime 函数中计算一下每一个指针绕一圈之后度数,并继续通过 CSS4 变量传递给 CSS

document.documentElement.style.setProperty('--toHour', `${hStart + 360}deg`); // 根据当前小时设置小时指针的旋转结束角度
document.documentElement.style.setProperty('--toMinute', `${mStart + 360}deg`); // 根据当前分钟设置分钟指针的旋转结束角度
document.documentElement.style.setProperty('--toSecond', `${sStart + 360}deg`); // 根据当前秒设置秒指针的旋转结束角度

这样,完整的设置实时时间的 setRealTime 函数就是这样的:

/**
 * @description 设置实时时间
*/
const setRealTime = () => {
  const now = new Date(); // 获取当前时间
  const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
  const sStart = 6 * second; // 设置秒指针的起始位置
  const mStart = 6 * minute + sStart / 60; // 设置分钟指针的起始位置
  const hStart = 30 * hour + mStart / 12; // 设置小时指针的起始位置

  document.documentElement.style.setProperty('--hour', `${hStart}deg`); // 根据当前小时设置小时指针的旋转角度
  document.documentElement.style.setProperty('--minute', `${mStart}deg`); // 根据当前分钟设置分钟指针的旋转角度
  document.documentElement.style.setProperty('--second', `${sStart}deg`); // 根据当前秒设置秒指针的旋转角度
  document.documentElement.style.setProperty('--toHour', `${hStart + 360}deg`); // 根据当前小时设置小时指针的旋转结束角度
  document.documentElement.style.setProperty('--toMinute', `${mStart + 360}deg`); // 根据当前分钟设置分钟指针的旋转结束角度
  document.documentElement.style.setProperty('--toSecond', `${sStart + 360}deg`); // 根据当前秒设置秒指针的旋转结束角度
}

传递的 --toHour--toMinute--toSecond 变量,需要在 CSS 中定义到动画的最后一帧中。于是我们在 CSS 分别定义三个指针的旋转动画

@keyframes hour {
  to {
    transform: rotate(var(--toHour));
  }
}
@keyframes minute {
  to {
    transform: rotate(var(--toMinute));
  }
}
@keyframes second {
  to {
    transform: rotate(var(--toSecond));
  }
}

 然后,在时针分针秒针元素对应的 CSS 中分别调用对应的动画:

.dial > .hour {
  top: 125px;
  width: 10px;
  height: 200px;
  transform-origin: center 175px;
  transform: rotate(var(--hour)) ;
  animation: hour 216000s linear infinite;
}
.dial > .minute {
  top: 25px;
  width: 7.5px;
  height: 300px;
  transform-origin: center 275px;
  transform: rotate(var(--minute));
  animation: minute 3600s linear infinite;
}
.dial > .second {
  top: 25px;
  width: 2.5px;
  height: 300px;
  transform-origin: center 275px;
  transform: rotate(var(--second));
  animation: second 60s linear infinite;
}

 注意:三个动画在调用的时候都设置了匀速重复;三个动画对应的时间都是秒数

看一下效果:

 

这样,我们就用相对理想的方式把我们的时钟给完成了。 


完整源代码

最后,还是把最终的完整源代码贡献出来,大家拿走不谢( ̄_, ̄ ) 

<!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 type="text/css">
body {
  background: #333;
}
.dial {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 600px;
  height: 600px;
  margin: auto;
  border: 5px solid #fff;
  border-radius: 50%;
}
.dial > span {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  display: block;
  width: 5px;
  height: 10px;
  margin: auto;
  background: #fff;
  transform-origin: center 300px;
}
.dial > span.bold {
  width: 7.5px;
  height: 20px;
}
.dial > .pointer {
  position: absolute;
  right: 0;
  left: 0;
  margin: auto;
  background: #fff;
  border-radius: 5px;
}
.dial > .hour {
  top: 125px;
  width: 10px;
  height: 200px;
  transform-origin: center 175px;
  transform: rotate(var(--hour)) ;
  animation: hour 216000s linear infinite;
}
.dial > .minute {
  top: 25px;
  width: 7.5px;
  height: 300px;
  transform-origin: center 275px;
  transform: rotate(var(--minute));
  animation: minute 3600s linear infinite;
}
.dial > .second {
  top: 25px;
  width: 2.5px;
  height: 300px;
  transform-origin: center 275px;
  transform: rotate(var(--second));
  animation: second 60s linear infinite;
}
@keyframes hour {
  to {
    transform: rotate(var(--toHour));
  }
}
@keyframes minute {
  to {
    transform: rotate(var(--toMinute));
  }
}
@keyframes second {
  to {
    transform: rotate(var(--toSecond));
  }
}
</style>
<script type="text/javascript">
/**
 * @description 创建刻度
*/
const createScale = () => {
  const oDiv = document.querySelector('.dial'); // 获取钟表盘面
  for (let i = 0; i < 60; i++) {
    const oSpan = document.createElement('span'); // 创建刻度元素
    oSpan.style.transform = `rotate(${6 * i}deg)`; // 设置旋转刻度属性
    if (i % 5 === 0) {
      oSpan.className = 'bold'; // 每 5 个刻度,让其显示突出一些
    }
    oDiv.appendChild(oSpan); // 将刻度放到钟表盘面
  }
  setRealTime(); // 设置实时时间
}

/**
 * @description 设置实时时间
*/
const setRealTime = () => {
  const now = new Date(); // 获取当前时间
  const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
  const sStart = 6 * second; // 设置秒指针的起始位置
  const mStart = 6 * minute + sStart / 60; // 设置分钟指针的起始位置
  const hStart = 30 * hour + mStart / 12; // 设置小时指针的起始位置

  document.documentElement.style.setProperty('--hour', `${hStart}deg`); // 根据当前小时设置小时指针的旋转角度
  document.documentElement.style.setProperty('--minute', `${mStart}deg`); // 根据当前分钟设置分钟指针的旋转角度
  document.documentElement.style.setProperty('--second', `${sStart}deg`); // 根据当前秒设置秒指针的旋转角度
  document.documentElement.style.setProperty('--toHour', `${hStart + 360}deg`); // 根据当前小时设置小时指针的旋转结束角度
  document.documentElement.style.setProperty('--toMinute', `${mStart + 360}deg`); // 根据当前分钟设置分钟指针的旋转结束角度
  document.documentElement.style.setProperty('--toSecond', `${sStart + 360}deg`); // 根据当前秒设置秒指针的旋转结束角度
}

window.onload = createScale;
</script>
</head>
<body>
  <div class="dial">
    <div class="pointer hour"></div>
    <div class="pointer minute"></div>
    <div class="pointer second"></div>
  </div>
</body>
</html>

篇幅有限,很多地方都没有过多展开讲解了。之后,我们会写出更多有趣的文章,大家敬请期待!

 

关注我,为您奉上更多精彩内容! 

  • 41
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vivado是一种由Xilinx开发的集成设计环境,主要用于FPGA芯片的设计和开发。"Vivado从此开始 进阶篇"是一本针对已经熟悉Vivado基本操作的用户的进阶学习资料,旨在帮助用户深入了解Vivado的更高级功能和使用技巧。 "Vivado从此开始 进阶篇"提供了丰富的实践案例和详细的操作指导,使用户能够更好地掌握Vivado的高级设计技巧。它包含了诸如时钟和时序约束、高级布局和布线技巧、逻辑优化和系统集成等内容。 时钟和时序约束是FPGA设计中非常重要的一部分,能够保证设计的正确性和稳定性。"Vivado从此开始 进阶篇"通过实例演示了如何正确地约束时钟和时序,包括设置时钟频率、时序路径等。 高级布局和布线技巧包括如何选择最佳的布局方式、减小信号路径的长度和延迟、优化布局和布线等。"Vivado从此开始 进阶篇"通过实例演示了如何使用Vivado的高级布局和布线功能,提高设计的性能。 逻辑优化和系统集成是优化设计的关键步骤。"Vivado从此开始 进阶篇"介绍了如何使用Vivado进行逻辑优化,包括使用优化指导工具、设置优化策略等。此外,它还介绍了如何进行系统集成,将各个模块进行连接和验证。 总之,"Vivado从此开始 进阶篇"是一本帮助用户更深入了解Vivado高级功能和使用技巧的学习资料。通过学习这本书,用户能够更好地应用Vivado进行FPGA设计和开发,并提高设计的准确性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值