这天逛博客看到好多大佬的网站上都有时钟贼拉有格调,就想自己也整一个,想到作为一个前端开发人员,体现前端开发技术的时刻到了<( ̄︶ ̄)>,用CSS画一个!先给大家看效果:

img

前置知识

下面来总结一下用到的知识点:

rotateZ()函数

rotateZ()这个CSS 函数定义了将元素在Z轴(就是数学坐标系X轴,Y轴,Z轴里的Z轴)上旋转而不使其变形的方法。 其运动的程度由指定的角度来定义;如果是正的,则为顺时针旋转,如果是负的,则是逆时针旋转。

rotate()函数

img

The rotate() CSS 函数 定义一个旋转属性,将元素在不变形的情况下旋转到不动点周围(如 transform-origin 属性所指定) 。 移动量由指定角度定义;如果为正值,则运动将为顺时针,如果为负值,则为逆时针 。 180°的旋转称为点反射 (point reflection)。

transform-origin属性

transform-origin CSS属性让你更改一个元素变形的原点。

时钟实现

  1. 画出表的圆心

    这里我用背景垂直水平居中+before伪类直接在背景上画出来的

  2. 画出时针、分针、秒针,并给它们添加定时任务,使其转起来

    这里就用到了rotateZ()函数,核心代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    <template>
    <div class="clock">
    <!--时针-->
    <div class="hour">
    <div ref="hr" class="hr"></div>
    </div>
    <!--分针-->
    <div class="min">
    <div ref="mn" class="mn"></div>
    </div>
    <!--秒针-->
    <div class="sec">
    <div ref="sc" class="sc"></div>
    </div>
    </div>
    </template>
    <script lang="ts">
    import { ref, onMounted, onUnmounted } from 'vue'
    export default {
    setup() {
    const hr = ref<HTMLElement | null>(null)
    const mn = ref<HTMLElement | null>(null)
    const sc = ref<HTMLElement | null>(null)
    const timeChange = () => {
    let day = new Date()
    let hh = day.getHours() * 30 // 一小时转30度
    let mm = day.getMinutes() * 6 // 一个刻度
    let ss = day.getSeconds() * 6 // 一个刻度
    ;(hr.value as HTMLElement).style.transform = `rotateZ(${hh + mm / 12}deg)`
    ;(mn.value as HTMLElement).style.transform = `rotateZ(${mm}deg)`
    ;(sc.value as HTMLElement).style.transform = `rotateZ(${ss}deg)`
    }
    let timer: any = null
    onMounted(() => {
    timeChange()
    timer = setInterval(timeChange, 1000)
    })
    onUnmounted(() => {
    timer && window.clearInterval(timer)
    })

    return {
    hr,
    mn,
    sc,
    }
    }
    }
    </script>

    <style scoped lang="scss">
    .clock {
    width: 300px;
    height: 300px;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;

    &:before {
    content: '';
    width: 15px;
    height: 15px;
    border-radius: 50%;
    position: absolute;
    z-index: 10;
    }

    .hour,
    .min,
    .sec {
    position: absolute;
    }

    .hour,
    .hr {
    width: 160px;
    height: 160px;
    }

    .min,
    .mn {
    width: 200px;
    height: 200px;
    }

    .sec,
    .sc {
    width: 230px;
    height: 230px;
    }

    .hr,
    .mn,
    .sc {
    display: flex;
    justify-content: center;
    border-radius: 50%;
    }

    .hr::before {
    content: '';
    width: 8px;
    height: 80px;
    //background-color: #b03232;
    border-radius: 6px;
    }

    .mn::before {
    content: '';
    width: 4px;
    height: 95px;
    //background-color: #fff;
    border-radius: 6px;
    }

    .sc::before {
    content: '';
    width: 2px;
    height: 150px;
    //background-color: #fff;
    border-radius: 4px;
    }
    }

    </style>

    实际的旋转中,是一个整个元素在旋转,而不是一个指针

    image-20211111115250680
  3. 画出表盘背景

    这里是画时钟最复杂的地方,如果只用rotate()transform-origin属性,我们会发现表盘刻度变成了这样

    image-20211111115602912

    这是为啥呢?原因很简单,因为在遍历过程中,每个刻度都有自己的高度,随着高度的累计,每个刻度的旋转的原点不一样了,简单来说就是圆心跟着动了。那么解决方案也就来了,只需要用position: absolute让它们的圆心一样就好了。

全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<template>
<div class="light">
<div class="clock">
<ul class="scaleBg">
<li v-for="i in 60" :key="i" class="scale" :style="scale(i)"></li>
<li v-for="i in 12" :key="i" class="scaleNum" :style="scaleNum(i)">
<span :style="fScaleNum(i)">{{ i }}</span>
</li>
</ul>
<div class="hour">
<div ref="hr" class="hr"></div>
</div>
<div class="min">
<div ref="mn" class="mn"></div>
</div>
<div class="sec">
<div ref="sc" class="sc"></div>
</div>
</div>
</div>
</template>

<script lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const hr = ref<HTMLElement | null>(null)
const mn = ref<HTMLElement | null>(null)
const sc = ref<HTMLElement | null>(null)

const timeChange = () => {
let day = new Date()
let hh = day.getHours() * 30 // 一小时转30度
let mm = day.getMinutes() * 6 // 一个刻度
let ss = day.getSeconds() * 6 // 一个刻度
;(hr.value as HTMLElement).style.transform = `rotateZ(${hh + mm / 12}deg)`
;(mn.value as HTMLElement).style.transform = `rotateZ(${mm}deg)`
;(sc.value as HTMLElement).style.transform = `rotateZ(${ss}deg)`
}

let timer: any = null

onMounted(() => {
timeChange()
timer = setInterval(timeChange, 1000)
})
onUnmounted(() => {
timer && window.clearInterval(timer)
})

const scale = (i: number) => {
if (i === 0 || i % 5 === 0) {
return {
transform: `rotate(${i * 6}deg)`,
height: '16px'
}
} else {
return {
transform: `rotate(${i * 6}deg)`
}
}
}
const scaleNum = (i: number) => {
if (i === 12) {
return {
left: '146px',
transform: `rotate(${i * 30}deg)`
}
} else {
return {
transform: `rotate(${i * 30}deg)`
}
}
}

const fScaleNum = (i: number) => {
// 把表盘刻度的字摆正
return {
display: 'block',
transform: `rotate(${-i * 30}deg)`
}
}

return {
hr,
mn,
sc,
scale,
scaleNum,
fScaleNum
}
}
}
</script>

<style scoped lang="scss">
.clock {
width: 300px;
height: 300px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
//box-shadow: 0 -15px 15px rgba(255, 255, 255, 0.05), inset 0 -15px 15px rgba(255, 255, 255, 0.05),
// 0 15px 15px rgba(255, 255, 255, 0.05), inset 0 15px 15px rgba(255, 255, 255, 0.05);

&:before {
content: '';
width: 15px;
height: 15px;
border-radius: 50%;
position: absolute;
//background: #fff;
z-index: 10;
}

.hour,
.min,
.sec {
position: absolute;
}

.hour,
.hr {
width: 160px;
height: 160px;
}

.min,
.mn {
width: 200px;
height: 200px;
}

.sec,
.sc {
width: 230px;
height: 230px;
}

.hr,
.mn,
.sc {
display: flex;
justify-content: center;
border-radius: 50%;
}

.hr::before {
content: '';
width: 8px;
height: 80px;
//background-color: #b03232;
border-radius: 6px;
}

.mn::before {
content: '';
width: 4px;
height: 95px;
//background-color: #fff;
border-radius: 6px;
}

.sc::before {
content: '';
width: 2px;
height: 150px;
//background-color: #fff;
border-radius: 4px;
}
}

.scaleBg {
position: relative;
width: 100%;
height: 100%;

.scale {
width: 2px;
height: 6px;
background: #fff;
position: absolute;
left: 150px;
top: 0;
transform-origin: center 150px;
}
.scaleNum {
position: absolute;
display: flex;
justify-content: center;
left: 148px;
top: 30px;
font-size: 12px;
transform-origin: center 120px;
}
}

.light {
.clock {
box-shadow: 0 -15px 15px rgba(255, 255, 255, 0.05),
inset 0 -15px 15px rgba(255, 255, 255, 0.05), 0 15px 15px rgba(255, 255, 255, 0.05),
inset 0 15px 15px rgba(255, 255, 255, 0.05);

&:before {
background: #181818;
}
.hr::before {
background-color: #b03232;
}
.mn::before {
background-color: #525252;
}
.sc::before {
background-color: #383737;
}
.scale {
background: #181818;
}
}
}

.dark {
.clock {
box-shadow: 0 -15px 15px rgba(255, 255, 255, 0.05),
inset 0 -15px 15px rgba(255, 255, 255, 0.05), 0 15px 15px rgba(255, 255, 255, 0.05),
inset 0 15px 15px rgba(255, 255, 255, 0.05);

&:before {
background: #fff;
}
.hr::before {
background-color: #b03232;
}
.mn::before {
background-color: #fff;
}
.sc::before {
background-color: #fff;
}
.scale {
background: #fff;
}
}
}
</style>

总结

技术要灵活运用呀,CSS真强呀!!