通过对样式篇的源代码进行观察,我们会发现,只用一个 div 元素做表盘,并使用对应的 before 和 after 两个伪元素做时针和分针,那么一共只能是三个元素。
需要显示秒针和表盘上的刻度,我们需要更多的元素来展示。那么今天,我们就把秒针和刻度显示出来,并且让时钟与真实的时间同步。
一、显示表盘刻度
我们之前使用的是一个 div 元素显示表盘,为了便于控制,我们给表盘一个 class 值:
<div class="dial"></div>
这样,在 css 中,就使用类选择器控制该 div:
.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::before,
.dial::after {
position: absolute;
top: 0;
bottom: 0;
left: 275px;
margin: auto;
background: #fff;
border-radius: 5px;
content: "";
transform-origin: 25px center;
}
.dial::before {
width: 300px;
height: 5px;
animation: clock 1s linear infinite;
}
.dial::after {
width: 200px;
height: 10px;
animation: clock 60s linear infinite;
}
表盘的刻度属于该 div 的子元素,并且有 60 个之多。于是我们通过 JavaScript 动态生成。
for (let i = 0; i < 60; i++) {
const oSpan = document.createElement('span'); // 创建刻度元素
}
这样,我们通过循环创建了 60 个 span 元素。然后,依次把这 60 个 span 元素放到 div 中:
const oDiv = document.querySelector('.dial'); // 获取钟表盘面
for (let i = 0; i < 60; i++) {
const oSpan = document.createElement('span'); // 创建刻度元素
oDiv.appendChild(oSpan); // 将刻度放到钟表盘面
}
然后,控制 span 元素的样式:
.dial > span {
position: absolute;
top: 0;
right: 0;
left: 0;
display: block;
width: 5px;
height: 10px;
margin: auto;
background: #fff;
}
然后,我们惊讶的看到了如下结果:
惊讶之余,我们想了一下。创建 60 个 span 元素,只用同一套 css 控制,css 中的定位控制只有一套,那么必然 60 个 span 元素重合了。
想要让这 60 个 span 元素围绕表盘平均排列,最好的做法就是让其围绕表盘中心旋转。于是我们设置 span 元素的旋转中心为表盘中心:
transform-origin: center 300px;
这样控制 span 的完整样式如下:
.dial > span {
position: absolute;
top: 0;
right: 0;
left: 0;
display: block;
width: 5px;
height: 10px;
margin: auto;
background: #fff;
transform-origin: center 300px;
}
然后,我们在创建 span 元素的时候控制每一个 span 元素的旋转角度:
const oDiv = document.querySelector('.dial'); // 获取钟表盘面
for (let i = 0; i < 60; i++) {
const oSpan = document.createElement('span'); // 创建刻度元素
oSpan.style.transform = `rotate(${6 * i}deg)`; // 设置旋转刻度属性
oDiv.appendChild(oSpan); // 将刻度放到钟表盘面
}
于是我们得到如下效果:
有那么一点点感觉了,但是不够。刻度是为了方便我们更快的读取时间,因此为了更好的用户体验,表盘刻度上每 5 个刻度,都是突出显示的。
于是,我们先定义一个用于突出显示的 span 元素的样式:
.dial > span.bold {
width: 7.5px;
height: 20px;
}
这样一来,我们就只需要每 5 个 span 元素就添加一个 class 的值为 bold 即可:
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); // 将刻度放到钟表盘面
}
于是我们就得到了一个想要的表盘:
最后,咱们把创建刻度的算法封装成一个函数:
/**
* @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); // 将刻度放到钟表盘面
}
}
这样,我们只需要在网页加载的时候直接调用该函数即可:
window.onload = createScale;
至此,我们已经有了我们想要的表盘。
二、重新创建时针、分针和秒针
有两点是我们需要考虑的:
- 时针、分针、秒针,是三个并列的元素,所以决定不再用伪元素,而是创建三个并列的 div 元素;
- 时针、分针、秒针都要根据真实时间不断旋转,既然在创建指针的时候已经获取了表盘的 div 元素,那么直接在 JavaScript 中创建这三个表示指针的 div 元素并加以控制,会更加方便。
于是,我们就写一个创建这三个指针元素的函数:
/**
* @description 创建指针
* @param {HTMLDIVElement} oDiv 钟表盘面元素
*/
const createPointer = (oDiv) => {
const oHour = document.createElement('div'); // 创建小时指针元素
const oMinute = document.createElement('div'); // 创建分钟指针元素
const oSecond = document.createElement('div'); // 创建秒指针元素
}
指针的 div 元素,还是一样使用 css 控制其显示样式,于是先定义这三个指针的显示样式:
.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;
}
.dial > .minute {
top: 25px;
width: 7.5px;
height: 300px;
transform-origin: center 275px;
}
.dial > .second {
top: 25px;
width: 2.5px;
height: 300px;
transform-origin: center 275px;
}
指针的初始位置和旋转的中心点就不做展开讲解了,对“样式篇”的思路理解的,这里一看就懂。
没错,我们需要给每一个指针都设置一个 class 的值为 pointer。并且,需要分别给时针、分针、秒针的 class 值添加 hour、minute、second 三个值以便单独控制显示样式:
/**
* @description 创建指针
* @param {HTMLDIVElement} oDiv 钟表盘面元素
*/
const createPointer = (oDiv) => {
const oHour = document.createElement('div'); // 创建小时指针元素
const oMinute = document.createElement('div'); // 创建分钟指针元素
const oSecond = document.createElement('div'); // 创建秒指针元素
oHour.className = 'pointer hour'; // 设置小时指针元素的 class 值
oMinute.className = 'pointer minute'; // 设置分钟指针元素的 class 值
oSecond.className = 'pointer second'; // 设置秒指针元素的 class 值
}
然后,我们还需要把这三个 div 元素都放到表盘 div 元素中:
/**
* @description 创建指针
* @param {HTMLDIVElement} oDiv 钟表盘面元素
*/
const createPointer = (oDiv) => {
const oHour = document.createElement('div'); // 创建小时指针元素
const oMinute = document.createElement('div'); // 创建分钟指针元素
const oSecond = document.createElement('div'); // 创建秒指针元素
oHour.className = 'pointer hour'; // 设置小时指针元素的 class 值
oMinute.className = 'pointer minute'; // 设置分钟指针元素的 class 值
oSecond.className = 'pointer second'; // 设置秒指针元素的 class 值
oDiv.appendChild(oHour); // 将小时指针放到钟表盘面
oDiv.appendChild(oMinute); // 将分钟指针放到钟表盘面
oDiv.appendChild(oSecond); // 将秒指针放到钟表盘面
}
由于我们是在页面加载的时候调用了创建刻度的 createScale 函数,于是我们可以在 createScale 函数的末尾调用创建指针的 createPointer 函数:
/**
* @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); // 将刻度放到钟表盘面
}
createPointer(oDiv); // 创建指针
}
于是,我们得到了如下的静态时钟:
没错,这一篇章里我们需要考虑通过真实时间计算并控制指针旋转角度。于是乎指针的初始位置和前面“样式篇”的水平向右不同,放到了竖直向上。
三、指针旋转并与真实时间同步
一样的思路,咱们先创建一个设置实时时间的函数吧:
/**
* @description 设置实时时间
* @param {HTMLDIVElement} oHour 小时指针元素
* @param {HTMLDIVElement} oMinute 分钟指针元素
* @param {HTMLDIVElement} oSecond 秒指针元素
*/
const setRealTime = (oHour, oMinute, oSecond) => {
const now = new Date(); // 获取当前时间
const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
}
时钟采用的是 12 小时制,于是每一个小时,时针旋转的度数为 30deg:
oHour.style.transform = `rotate(${30 * hour}deg)`; // 根据当前小时设置小时指针的旋转角度
下午的小时数大于 12,这个时候计算出来的角度会大于 360deg,但是由于到了中午 12 点,时针将会回到原点,所以从显示层面,下午的小时度数不受影响。
一小时有 60 分钟,因此每一分钟,分针旋转角度为 6deg:
oMinute.style.transform = `rotate(${6 * minute}deg)`; // 根据当前分钟设置分钟指针的旋转角度
同样,一分钟有 60 秒,因此每一秒钟,秒针旋转角度也为 6deg:
oSecond.style.transform = `rotate(${6 * second}deg)`; // 根据当前秒设置秒指针的旋转角度
放到整个函数中:
/**
* @description 设置实时时间
* @param {HTMLDIVElement} oHour 小时指针元素
* @param {HTMLDIVElement} oMinute 分钟指针元素
* @param {HTMLDIVElement} oSecond 秒指针元素
*/
const setRealTime = (oHour, oMinute, oSecond) => {
const now = new Date(); // 获取当前时间
const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
oHour.style.transform = `rotate(${30 * hour}deg)`; // 根据当前小时设置小时指针的旋转角度
oMinute.style.transform = `rotate(${6 * minute}deg)`; // 根据当前分钟设置分钟指针的旋转角度
oSecond.style.transform = `rotate(${6 * second}deg)`; // 根据当前秒设置秒指针的旋转角度 setTimeout(() => setRealTime(oHour, oMinute, oSecond), 1000); // 每隔一秒钟更新一次时间及其指针旋转角度
}
同样的道理,我们可以在创建指针的 createPointer 函数的末尾调用设置实时时间的 setRealTime 函数。
/**
* @description 创建指针
* @param {HTMLDIVElement} oDiv 钟表盘面元素
*/
const createPointer = (oDiv) => {
const oHour = document.createElement('div'); // 创建小时指针元素
const oMinute = document.createElement('div'); // 创建分钟指针元素
const oSecond = document.createElement('div'); // 创建秒指针元素
oHour.className = 'pointer hour'; // 设置小时指针元素的 class 值
oMinute.className = 'pointer minute'; // 设置分钟指针元素的 class 值
oSecond.className = 'pointer second'; // 设置秒指针元素的 class 值
oDiv.appendChild(oHour); // 将小时指针放到钟表盘面
oDiv.appendChild(oMinute); // 将分钟指针放到钟表盘面
oDiv.appendChild(oSecond); // 将秒指针放到钟表盘面
setRealTime(oHour, oMinute, oSecond); // 设置实时时间
}
于是,我们得到了如下结果:
最后,我们需要让时钟动起来。该怎么做呢?
在设置实时时间的 setRealTime 函数中,已经完成了获取当前时间并让指针显示的功能。于是我们直接在这个 setRealTime 函数中,每一秒钟递归调用一次该函数,就可以实现时钟的走动:
/**
* @description 设置实时时间
* @param {HTMLDIVElement} oHour 小时指针元素
* @param {HTMLDIVElement} oMinute 分钟指针元素
* @param {HTMLDIVElement} oSecond 秒指针元素
*/
const setRealTime = (oHour, oMinute, oSecond) => {
const now = new Date(); // 获取当前时间
const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
oHour.style.transform = `rotate(${30 * hour}deg)`; // 根据当前小时设置小时指针的旋转角度
oMinute.style.transform = `rotate(${6 * minute}deg)`; // 根据当前分钟设置分钟指针的旋转角度
oSecond.style.transform = `rotate(${6 * second}deg)`; // 根据当前秒设置秒指针的旋转角度
setTimeout(() => setRealTime(oHour, oMinute, oSecond), 1000); // 每隔一秒钟更新一次时间及其指针旋转角度
}
这样,我们通过 setTimeout() 方法,设置延迟 1000ms 调用一次设置实时时间的 setRealTime 函数,通过巧妙的递归,完成了时钟的走动。
这样,我们就把一个外观相对逼真,时间展示真实时间的时钟写出来了!
完整源码
附上完整源码,拿走不谢O(∩_∩)O
<!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;
}
.dial > .minute {
top: 25px;
width: 7.5px;
height: 300px;
transform-origin: center 275px;
}
.dial > .second {
top: 25px;
width: 2.5px;
height: 300px;
transform-origin: center 275px;
}
</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); // 将刻度放到钟表盘面
}
createPointer(oDiv); // 创建指针
}
/**
* @description 创建指针
* @param {HTMLDIVElement} oDiv 钟表盘面元素
*/
const createPointer = (oDiv) => {
const oHour = document.createElement('div'); // 创建小时指针元素
const oMinute = document.createElement('div'); // 创建分钟指针元素
const oSecond = document.createElement('div'); // 创建秒指针元素
oHour.className = 'pointer hour'; // 设置小时指针元素的 class 值
oMinute.className = 'pointer minute'; // 设置分钟指针元素的 class 值
oSecond.className = 'pointer second'; // 设置秒指针元素的 class 值
oDiv.appendChild(oHour); // 将小时指针放到钟表盘面
oDiv.appendChild(oMinute); // 将分钟指针放到钟表盘面
oDiv.appendChild(oSecond); // 将秒指针放到钟表盘面
setRealTime(oHour, oMinute, oSecond); // 设置实时时间
}
/**
* @description 设置实时时间
* @param {HTMLDIVElement} oHour 小时指针元素
* @param {HTMLDIVElement} oMinute 分钟指针元素
* @param {HTMLDIVElement} oSecond 秒指针元素
*/
const setRealTime = (oHour, oMinute, oSecond) => {
const now = new Date(); // 获取当前时间
const [hour, minute, second] = [now.getHours(), now.getMinutes(), now.getSeconds()]; // 取出当前小时、分钟和秒
oHour.style.transform = `rotate(${30 * hour}deg)`; // 根据当前小时设置小时指针的旋转角度
oMinute.style.transform = `rotate(${6 * minute}deg)`; // 根据当前分钟设置分钟指针的旋转角度
oSecond.style.transform = `rotate(${6 * second}deg)`; // 根据当前秒设置秒指针的旋转角度
setTimeout(() => setRealTime(oHour, oMinute, oSecond), 1000); // 每隔一秒钟更新一次时间及其指针旋转角度
}
window.onload = createScale;
</script>
</head>
<body>
<div class="dial"></div>
</body>
</html>
↓
↓
↓
由于时针、分针、秒针都是通过 JavaScript 创建的,导致 DOM 控制的成本较高,也让 JavaScript 的代码较多。于是,我们可以尝试使用 CSS4 中的变量进行优化。尽情期待我们的“写个时钟(进阶篇)”!