theme: juejin
前言
效果来自一款屏保软件:world-clock,觉得挺有意思的,于是深夜决定自己动手撸一个。
效果预览
在线预览:罗盘时钟
预览截图:
动手实现
首先是要将多个文字元素呈圆形排布,之后将圆形以一定的角度定时旋转来达到效果。
接下来首先实现如何将文字呈圆形排布,搞定了这个,后面的也就不难了。
将文字呈圆形排布
接下来我们先来一步一步实现最外围的“秒”,秒有0-59一共60个元素,首先将他们定位在一个正方形div里。
<template>
<div class="home">
<!-- 秒 -->
<div class="box-wrapper">
<div class="circle-box" :style="boxStyle('seconds')">
<span
v-for="(item, index) in secondTexts"
:key="item"
>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
secondTexts: [
'零零', '一秒', '二秒', '三秒', '四秒', '五秒', '六秒', '七秒', '八秒', '九秒', '十秒',
'十一秒', '十二秒', '十三秒', '十四秒', '十五秒', '十六秒', '十七秒', '十八秒', '十九秒', '二十秒',
'二十一秒', '二十二秒', '二十三秒', '二十四秒', '二十五秒', '二十六秒', '二十七秒', '二十八秒', '二十九秒', '三十秒',
'三十一秒', '三十二秒', '三十三秒', '三十四秒', '三十五秒', '三十六秒', '三十七秒', '三十八秒', '三十九秒', '四十秒',
'四十一秒', '四十二秒', '四十三秒', '四十四秒', '四十五秒', '四十六秒', '四十七秒', '四十八秒', '四十九秒', '五十秒',
'五十一秒', '五十二秒', '五十三秒', '五十四秒', '五十五秒', '五十六秒', '五十七秒', '五十八秒', '五十九秒'
],
// 盒子大小
boxSize: {
seconds: 580
}
}
},
methods: {
// 设置文字外围盒子宽高
boxStyle(key) {
return {
width: this.boxSize[key] + 'px',
height: this.boxSize[key] + 'px'
}
}
}
}
</script>
<style lang="scss" scoped>
.home {
height: 100%;
width: 100%;
background-color: #000000;
color: #71767D;
position: relative;
min-width: 800px;
min-height: 660px;
padding: 20px 0;
overflow: hidden;
}
.box-wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.circle-box {
position: relative;
border: 1px solid red;
}
.circle-box span {
white-space: nowrap;
font-size: 14px;
position: absolute;
}
</style>
现在文字设置absolute定位后都堆叠在一起了,接下来通过位置计算让其呈圆形排布。
从图中我们知道,要让文字排列在圆上,我们需要知道文字元素到O点(圆心)的横坐标和纵坐标,也就是a的高度和b的长度,对应我们要设置定位的top和left值。
半径我们是知道的,即盒子宽度的一半 r = 580 / 2 = 290,每个元素与圆心的夹角c = (360 / 60) * i,据此根据数学公式我们可以求出a和b的值。新增代码如下:
<template>
<div class="home">
<!-- 秒 -->
<div class="box-wrapper">
<div class="circle-box" :style="boxStyle('seconds')">
<span
v-for="(item, index) in secondTexts"
:key="item"
:style="spanStyle(boxSize.seconds, secondTexts, index)"
>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
methods: {
spanStyle(size, texts, i) {
const r = size / 2 // 半径
const deg = this.getPerDeg(texts) // 元素平均间隔度数
const angle = i * deg // 夹角
const { a, b } = this.getHypotenuse(r, angle)
const rotateDeg = deg * i // 文字旋转角度
return {
top: a + r + 'px',
left: b + r + 'px',
transform: `rotate(${rotateDeg}deg)`,
transformOrigin: '0 0'
}
},
// 元素平均间隔度数
getPerDeg(texts) {
return 360 / texts.length
},
// 已知角度和斜边,获取直角边
getHypotenuse(long, angle) {
// 获得弧度
let radian = 2 * Math.PI / 360 * angle
return {
a: Math.sin(radian) * long, // 邻边
b: Math.cos(radian) * long // 对边
}
}
}
}
</script>
效果不错,接下来发挥cv大法,把分钟纬度给弄上去。新增代码如下:
<template>
<div class="home">
<!-- 分 -->
<div class="box-wrapper">
<div class="circle-box" :style="boxStyle('minutes', minutesDeg)">
<span
v-for="(item, index) in minuteTexts"
:key="item"
:style="spanStyle(boxSize.minutes, minuteTexts, index)"
>{{ item }}</span>
</div>
</div>
<!-- 秒 -->
<div class="box-wrapper">
<div class="circle-box" :style="boxStyle('seconds', secondsDeg)">
<span
v-for="(item, index) in secondTexts"
:key="item"
:style="spanStyle(boxSize.seconds, secondTexts, index)"
>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
secondTexts: [
'零零', '一秒', '二秒', '三秒', '四秒', '五秒', '六秒', '七秒', '八秒', '九秒', '十秒',
'十一秒', '十二秒', '十三秒', '十四秒', '十五秒', '十六秒', '十七秒', '十八秒', '十九秒', '二十秒',
'二十一秒', '二十二秒', '二十三秒', '二十四秒', '二十五秒', '二十六秒', '二十七秒', '二十八秒', '二十九秒', '三十秒',
'三十一秒', '三十二秒', '三十三秒', '三十四秒', '三十五秒', '三十六秒', '三十七秒', '三十八秒', '三十九秒', '四十秒',
'四十一秒', '四十二秒', '四十三秒', '四十四秒', '四十五秒', '四十六秒', '四十七秒', '四十八秒', '四十九秒', '五十秒',
'五十一秒', '五十二秒', '五十三秒', '五十四秒', '五十五秒', '五十六秒', '五十七秒', '五十八秒', '五十九秒'
],
minuteTexts: [
'零零', '一分', '二分', '三分', '四分', '五分', '六分', '七分', '八分', '九分', '十分',
'十一分', '十二分', '十三分', '十四分', '十五分', '十六分', '十七分', '十八分', '十九分', '二十分',
'二十一分', '二十二分', '二十三分', '二十四分', '二十五分', '二十六分', '二十七分', '二十八分', '二十九分', '三十分',
'三十一分', '三十二分', '三十三分', '三十四分', '三十五分', '三十六分', '三十七分', '三十八分', '三十九分', '四十分',
'四十一分', '四十二分', '四十三分', '四十四分', '四十五分', '四十六分', '四十七分', '四十八分', '四十九分', '五十分',
'五十一分', '五十二分', '五十三分', '五十四分', '五十五分', '五十六分', '五十七分', '五十八分', '五十九分'
],
// 盒子大小
boxSize: {
seconds: 580,
minutes: 440
}
}
}
}
</script>
实现罗盘转动
新增代码如下:
<template>
<div class="home">
<!-- 分 -->
<div class="box-wrapper">
<div class="circle-box" :style="boxStyle('minutes', minutesDeg)">
<span
v-for="(item, index) in minuteTexts"
:key="item"
:style="spanStyle(boxSize.minutes, minuteTexts, index)"
:class="{'active': index === currentMinutes}"
>{{ item }}</span>
</div>
</div>
<!-- 秒 -->
<div class="box-wrapper">
<div class="circle-box" :style="boxStyle('seconds', secondsDeg)">
<span
v-for="(item, index) in secondTexts"
:key="item"
:style="spanStyle(boxSize.seconds, secondTexts, index)"
:class="{'active': index === currentSeconds}"
>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
currentMinutes: 0, // 当前-分钟
currentSeconds: 0, // 当前-秒
minutesDeg: 0, // 当前-分钟-转动角度
secondsDeg: 0, // 当前-面-转动角度
timer: null // 定时器
}
},
mounted() {
this.init()
},
methods: {
init() {
const d = new Date()
const minutes = d.getMinutes() // 分
const seconds = d.getSeconds() // 秒
// 当前时间
this.currentMinutes = minutes
this.currentSeconds = seconds
// 角度
this.minutesDeg = this.currentMinutes * this.getPerDeg(this.minuteTexts)
this.secondsDeg = this.currentSeconds * this.getPerDeg(this.secondTexts)
// 设置定时器
this.timer = setInterval(() => {
this.runClock()
}, 1000)
// 记得清除定时器
this.$once('hook:beforeDestroy', () => {
clearInterval(this.timer)
})
},
boxStyle(key, deg) {
return {
// 设置文字外围盒子宽度、高度
width: this.boxSize[key] + 'px',
height: this.boxSize[key] + 'px',
// 添加转动
transform: `rotate(-${deg}deg)`
}
},
// 元素平均间隔度数
getPerDeg(texts) {
return 360 / texts.length
},
runClock() {
const d = new Date()
const minutes = d.getMinutes() // 分
const seconds = d.getSeconds() // 秒
if (this.currentMinutes !== minutes) {
this.currentMinutes = minutes
this.minutesDeg += this.getPerDeg(this.minuteTexts)
}
this.currentSeconds = seconds
this.secondsDeg += this.getPerDeg(this.secondTexts)
}
}
}
</script>
<style lang="scss" scoped>
.circle-box {
position: relative;
// 添加动画效果
transition: transform 0.4s ease-in-out;
}
// 激活时文字颜色为白色
.circle-box span.active {
color: #fff;
}
</style>
至此,整个工作就完成得差不多了,剩余的月、日、时等纬度也就是依样画葫芦的事,这里就不一一贴出来了,感兴趣的同学可以移步GitHub查看完整代码。
完整代码地址:GitHub
结尾
本文到这里就结束了,感谢阅读,码字不易,欢迎你的点赞👍!!!
本文如果有什么错误的地方,也欢迎评论区指正、交流!
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 8号的凌晨4点 原文链接:https://juejin.im/post/6974173265526784037