先给大家看看时钟的效果
当然我的UI不咋地,随便看看就好了。
大家可能以为我写在这个demo的代码肯定很多,但是我可以负责任的告诉你们
js代码其实少的可怜,这个时钟demo我只需要获取到三个数值
hour小时,minute分钟,second秒
并且定时每秒获取一次就可以了。
那么我是怎么做到的呢?
BaseClock -->
<script setup>
import { useClock } from "@/hooks/useClock";
const { hour, minute, second } = useClock();
</script>
hooks/useClock.js -->
import { ref, computed } from 'vue';
import { useSafeInterval } from '@/hooks/useSafeListener';
export function useClock() {
const newDate = ref(new Date());
const hour = computed(() => newDate.value.getHours());
const minute = computed(() => newDate.value.getMinutes());
const second = computed(() => newDate.value.getSeconds());
function setTimestamp() {
newDate.value = new Date()
}
useSafeInterval(() => {
setTimestamp();
});
return {
hour,
minute,
second,
}
}
可以看到我用到了之前写的 useSafeInterval 这个自定义hook,
能够让我安全的使用定时器而不用担心产生副作用。
我把当前时间储存在newDate这个变量中,并且定时获取他,让他保持在当前时间点上。
然后再用计算属性储存他的三个值就好了。
<div class="clock" :class="[`clock-${theme}`]">
<ul class="scales">
<li
v-for="(item, index) in 12"
:key="index"
:style="`--scale:${(index * 360) / 12}deg`"
>
<span
class="scale-text"
:style="`--scale:${-((index * 360) / 12)}deg`"
>{{ index }}</span
>
</li>
</ul>
<div class="hour" :style="`--scale:${hour * 30 + (minute / 2)}deg`"></div>
<div class="minute" :style="`--scale:${minute * 6 + (second / 10)}deg`"></div>
<div class="second" :style="`--scale:${second * 6}deg`"></div>
<div class="dot"></div>
</div>
这部分是定时器的html
clock是整个定时器的容器部分
ul.scales > li 是每个小时的刻度
在没有做定位之前是这样的
它们就是一个正常的文档流
我是如何做它们的定位的呢
这得使用position做定位,然后用transform旋转它们。
首先定位它们一下吧。
我的思路是先将它们的位置居中,然后顶部
.scales {
position: relative;
height: 100%;
li {
position: absolute;
top: 0;
left: 50%;
height: 100%;
transform: translateX(-50%);
}
}
然后可以看到它们都被重叠在一起了,高度呢也是整个时钟的100%。
那么这个时候如何旋转它们呢,在li标签里我设置了一个css变量
:style="`--scale:${(index * 360) / 12}deg`"
它的数值是v-for中的index * 360 / 12,我文中这样写是为了代码更加清楚它的数值,
其实写一个index * 30 也是一样的(360 / 12 = 30)
然后用rotateZ(var(--scale))为它们每一个做旋转,
.scales {
position: relative;
height: 100%;
li {
position: absolute;
top: 0;
left: 50%;
height: 100%;
transform: translateX(-50%) rotateZ(var(--scale));
font-size: 12px;
padding-top: 4px;
}
}
但是这样看起来不太对劲,我得把数字掰正才好,当然只需要将li旋转的数值反过来就行了。
<span class="scale-text" :style="`--scale:${-((index * 360) / 12)}deg`" >{{ index }}</span >
.scales {
position: relative;
height: 100%;
li {
position: absolute;
top: 0;
left: 50%;
height: 100%;
transform: translateX(-50%) rotateZ(var(--scale));
font-size: 12px;
padding-top: 4px;
.scale-text {
display: block;
transform: rotateZ(var(--scale));
}
}
}
那么接下来的指针就好办了。
<div class="hour" :style="`--scale:${hour * 30}deg`"></div>
<div class="minute" :style="`--scale:${minute * 6}deg`"></div>
<div class="second" :style="`--scale:${second * 6}deg`"></div>
<div class="dot"></div>
分针和秒针需要进行1/60的刻度,而时针进行的是1/12的刻度。
360 / 60 = 6
360 / 12 = 30
照着上面的刻度写一遍样式
.hour,
.minute,
.second {
border-radius: 6px;
position: absolute;
top: 50%;
left: 50%;
transform-origin: bottom center;
transform: translate(-50%, -100%) rotateZ(var(--scale));
}
.hour {
height: 20%;
width: 3px;
}
.minute {
height: 26%;
width: 2px;
}
.second {
height: 38%;
width: 1px;
}
当然这个并不是最正确的答案,真正的时钟,时针和分钟的度数并不会保持在一个地方太久,它都是证据分钟的刻度变化的
<p>{{hour * 30 + (minute / 2)}}</p>
<p>{{minute * 6 + (second / 10) }}</p>
<p>{{second * 6}}</p>
分针在时钟上,每一刻度为 360 / 60 = 6
秒钟每一秒增加分针的度数为 6/60 = 0.1
所以每一秒增加0.1的度数,让分针每秒加上0.1也就是秒数除以10就行了。
时针每一刻为 360/12=30
每分钟在时针上增加 30/60 = 0.5
所以让时针加上分针/2的数值就行了
当然至于秒钟在分针上增加的刻度我算了一下,大约每秒0.008333度,实在没法看,所以不加了。
有兴趣的朋友可以加一下。
上面有部分是没有说的,不过关系不大,就那个每小时旁边的杠杠
<ul class="scales scales-BA">
<li v-for="(item, index) in 60" :key="index" :style="`--scale:${index * 6}deg`"
v-show="index % 5 != 0">
<div class="scale"></div>
</li>
</ul>
.scales-BA {
top: -100%;
.scale {
height: 4px;
width: 1px;
}
}
整一个刻度的复制,数量为60也就是每分钟,然后余5为0不显示就行了。
当然top-100%是为了对齐时钟的内容,不然是溢出的。
以下是所有的代码
BaseClock.vue
<template>
<div class="base-clock">
<div>
<label for="dark">
<input type="radio" name="theme" id="dark" value="dark" v-model="theme" />dark
</label>
<br />
<label for="light">
<input type="radio" name="theme" id="light" value="light" v-model="theme" />light
</label>
</div>
<div class="clock" :class="[`clock-${theme}`]">
<ul class="scales">
<li v-for="(item, index) in 12" :key="index"
:style="`--scale:${(index * 360) / 12}deg`">
<span class="scale-text" :style="`--scale:${-((index * 360) / 12)}deg`">{{ index }}</span>
</li>
</ul>
<ul class="scales scales-BA">
<li v-for="(item, index) in 60" :key="index" :style="`--scale:${index * 6}deg`"
v-show="index % 5 != 0">
<div class="scale"></div>
</li>
</ul>
<div class="hour" :style="`--scale:${hour * 30 + (minute / 2)}deg`"></div>
<div class="minute" :style="`--scale:${minute * 6 + (second / 10)}deg`"></div>
<div class="second" :style="`--scale:${second * 6}deg`"></div>
<div class="dot"></div>
</div>
</div>
</template>
<script setup>
import { ref, defineProps, toRefs } from "vue";
import { useClock } from "@/hooks/useClock";
const props = defineProps({
size: {
type: Number,
default: 180,
},
themeType: {
type: String,
default: "light",
vaildator: (value) => ["dark", "light"].includes(value),
},
});
const { size } = toRefs(props);
const theme = ref(props.themeType);
const clockSize = `${size.value}px`;
const { hour, minute, second } = useClock();
</script>
<style lang="scss" scoped>
.clock {
width: v-bind("clockSize");
height: v-bind("clockSize");
border-radius: 50%;
position: relative;
&-dark {
background-color: rgba($color: #000000, $alpha: 0.8);
.scales li {
color: #fff;
}
.hour,
.minute,
.second,
.scales-BA .scale {
background-color: rgb(255, 255, 255);
}
}
&-light {
background-color: #ffffff;
box-shadow: inset 0 0 2px 0 rgba($color: #000000, $alpha: 1);
.scales li {
color: inherit;
}
.hour,
.minute,
.second,
.scales-BA .scale {
background-color: #000;
}
}
.dot {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 4px;
height: 4px;
border-radius: 50%;
background-color: red;
}
.scales {
position: relative;
height: 100%;
li {
position: absolute;
top: 0;
left: 50%;
height: 100%;
transform: translateX(-50%) rotateZ(var(--scale));
font-size: 12px;
padding-top: 4px;
.scale-text {
display: block;
transform: rotateZ(var(--scale));
position: relative;
}
}
}
.scales-BA {
top: -100%;
.scale {
height: 4px;
width: 1px;
}
}
.hour,
.minute,
.second {
border-radius: 6px;
position: absolute;
top: 50%;
left: 50%;
transform-origin: bottom center;
transform: translate(-50%, -100%) rotateZ(var(--scale));
}
.hour {
height: 20%;
width: 3px;
}
.minute {
height: 26%;
width: 2px;
}
.second {
height: 38%;
width: 1px;
}
}
</style>
hooks/useClock.js
import { ref, computed } from 'vue';
import { useSafeInterval } from '@/hooks/useSafeListener';
export function useClock() {
const newDate = ref(new Date());
const hour = computed(() => newDate.value.getHours());
const minute = computed(() => newDate.value.getMinutes());
const second = computed(() => newDate.value.getSeconds());
function setTimestamp() {
newDate.value = new Date()
}
useSafeInterval(() => {
setTimestamp();
});
return {
hour,
minute,
second,
}
}
安全定时器 useSafeInterval
可以在我之前的帖子里找到。当然你也可以自己写一个,主要是在组件销毁前自动销毁定时器就行了。
我写这个时钟的时候还顺便弄了两个主题,
感觉还可以。
不喜欢的也可以去掉,保留一个或者自己写。
好了,这篇就到这里,感谢各位收看。
这里是海星吧,我们下次见,拜拜
最后于 2022-4-9
被海星吧编辑
,原因: 完成帖子。