前言
前段时间看抖音,有人用时间轮盘作为动态的桌面壁纸,感觉很好玩,于是突发奇想,可以用JS来实现这个功能。
来来来,先看看成果
这个效果实现起来其实只有1个难点(其他都不是事),难点就是:元素圆形布局。
效果示意图
居然是圆,那我们肯定要知道圆心,和半径了,这样才能确定一个圆。
先看看上面这个效果图
解析:
1、圆心:O点、半径r;
2、圆心角:∠BOM;
3、需要布局的元素:A、B、C、D、E、F、G、H绝对定位的DIV元素;
4、DIV绝对定位时的元素的坐标点,可以用left值 和 top值
根据上图,我们知道圆心坐标,半径r的值,那就很容易计算B元素的lef和top。
OK,有了这些信息,我们还需要一些数学知识,先来了解一波,然后再开始制作。
概念定义
1、弧度:弧度是角的度量单位。
弧长等于半径的弧,其所对的圆心角为1弧度。(即两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角的弧度为1)。
根据定义,一周的弧度数为2πr/r=2π,360°角=2π弧度,因此,1弧度约为57.3°,即57°17’44.806’’,1°为π/180弧度,近似值为0.01745弧度,周角为2π弧度,平角(即180°角)为π弧度,直角为π/2弧度。
弧长=n2πr/360 (在这里n就是角度数,即圆心角n所对应的弧长。)
2、正弦值:弦值是在直角三角形中,对边的长比上斜边的长的值。
Math.sin(x) : x 必需。一个以弧度表示的角。将角度乘以 0.017453293 (2PI/360)即可转换为弧度。
3、余弦值:是指直角三角形锐角邻边与斜边的比值。
有了这些基础知识,我们就可以正式开始用代码实现元素圆形布局了。
JS如何实现元素圆形布局了???
圆心角∠BOM是多少度?上面的图形,就是一个圆平均分成了8分,所以每份角度是: 360°/8。
有了角度,和半径r,我们就知道了MB的长度,即可知道B元素top值:
对边(MB ) = Math.sin(X)* 斜边® = Math.sin( (360/8)* PI/180 )* r
同理,我们也就知道了OM,即可知道B元素的left值:
邻边(OM) = Math.COS(X索引) 200
索引即当前元素是第几个
<style>
.container{
position:absolute;
width:800px;
height:800px;
}
.box{
position:absolute;
width: 50px;
min-height:50px;
background:#000;
}
.dot{
position:absolute;
width:8px;
height:8px;
background:#F00;
}
</style>
<div class="container">
<div class="dot"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
<script>
$(function(){
// 盒子元素
var el = $(".container");
//中心点横坐标
var dotLeft = (el.width()-$(".dot").width())/2;
//中心点纵坐标
var dotTop = (el.height()-$(".dot").height())/2;
//起始角度
var stard = 0;
//半径
var radius = 200;
//每一个BOX对应的角度;
var avd = 360/$(".box").length;
//每一个BOX对应的弧度;
var ahd = avd*Math.PI/180;
//设置圆的中心点的位置
$(".dot").css({
"left": dotLeft,
"top": dotTop
});
$(".box").each(function(index, element){
$(this).css({
"left": Math.sin((ahd*index))*radius+dotLeft,
"top": Math.cos((ahd*index))*radius+dotTop
});
});
})
</script>
这时候会发现,效果不尽人意,圆心和各个元素位置存在偏差。这个差距恰好是每个元素宽高的一半,所以我们进一步处理,同时,对js做一下简单的封装,供后续使用
/**
* el是容器元素
* radius是半径
* cb是回调方法
*/
function circle(el, radius, cb) {
var el = $(el);
// 圆心
var circlec = el.find('.dot');
var box = el.find('.box');
var dotLeft = (el.width()-circlec.width())/2;
var dotTop = (el.height()-circlec.height())/2;
var stard = 0;
var radius = radius;
var avd = 360/box.length;
var ahd = avd*Math.PI/180;
//设置圆的中心点的位置
circlec.css({
"left":dotLeft,
"top":dotTop
});
box.each(function(index, element){
$(this).css({
'left': Math.sin((ahd*index))*radius+dotLeft-box.width()/2,
'top': Math.cos((ahd*index))*radius+dotTop-box.height()/2,
// 'transform': 'rotate(-'+ avd * index +'deg)' 这里注释,后续还会加上
});
});
// 这里减去box.width()/2就是为了中心点和元素刚好是正中心位置。
cb && cb(avd);
}
调用上面方法即可
<div class="container year">
<div class="dot"></div>
<div class="box"></div>
...
</div>
<script>
circle('.year', 200)
</script>
有了圆形布局,一切就很简单了。下面我们吧日期填充到元素上面即可,同时注意每个圆的半径,不然其重合。
下面用到了moment.js,没有了解过的,可以先了解一波《moment.js日期时间管理的常用方法详细教程》
年份轮盘
先收集今年后10年的年份数据,并把数字转换成大写。
createYear();
function createYear() {
// 今年
var thisYear = moment().format('YYYY');
// 今年 + 10年
var futureYear = moment().add(10, 'Y').format('YYYY');
var yearlistText = function(list){
var list = list.map(item=>{
return albToCn(item+'');
})
// console.log(list);
list.forEach(item=>{
$('.year').append('<div class="box">'+item+'</div>')
})
circle('.year', 100)
}
// 递归来生成后10年的数组,如果大于截止年份,就调用yearlistText方法来,把数字转成大写,生成新的数组,并在year元素中append box元素,后再调用circle来让元素圆形布局
var yearlist = [];
var createList = function(year) {
if(year<=futureYear){
yearlist.push(year);
year++;
createList(year);
} else {
// console.log(yearlist);
yearlistText(yearlist);
}
}
createList(thisYear);
}
function albToCn(value) {
var words = [];
for(var i=0;i<value.length;i++){
var word = value.charAt(i);
switch (word){
case '0':
words.push('零');
break;
case '1':
words.push('一');
break;
case '2':
words.push('二');
break;
case '3':
words.push('三');
break;
case '4':
words.push('四');
break;
case '5':
words.push('五');
break;
case '6':
words.push('六');
break;
case '7':
words.push('七');
break;
case '8':
words.push('八');
break;
case '9':
words.push('九');
break;
}
}
return words.join('');
}
这里把box的宽度调小,刚好只能装下一个文字的宽度,就可以使文字竖着。同时把box颜色去掉。最终样式和html主体元素
<style>
.date{
position:relative;
width:800px;
height:800px;
}
.container{
position:absolute;
width:800px;
height:800px;
margin:0 auto;
}
.box{
position:absolute;
width:18px;
min-height:20px;
/* background:#000; */
}
.dot{
position:absolute;
width:2px;
height:2px;
background:#F00;
}
</style>
<div class="date">
<div class="year container"></div>
<div class="month container"></div>
<div class="days container"></div>
<div class="hours container"></div>
<div class="minute container"></div>
<div class="second container"></div>
</div>
是不是同样很奇怪了,元素同样需要旋转一个角度,而这个角度刚好是每一个BOX对应的角度。
在circle方法里面把每个box加上旋转
function circle(el, radius, cb) {
var el = $(el);
...
...
box.each(function(index, element){
$(this).css({
...
'transform': 'rotate(-'+ avd * index +'deg)' // 这里注释去掉
});
});
...
}
这样就搞定了年份轮盘了。
月份、日期、小时轮盘
有了上面的经验,后面制作就更简单。对创建元素也进行一次封装。CN的定义主要是把数组转成中文汉字,很lou,大家可以用其他办法。
const CN = [{ value: 1, label: '一' },{ value: 2, label: '二' },{ value: 3, label: '三' },{ value: 4, label: '四' },{ value: 5, label: '五' },{ value: 6, label: '六' },{ value: 7, label: '七' },{ value: 8, label: '八' },{ value: 9, label: '九' },{ value: 10, label: '十' },{ value: 11, label: '十一' },{ value: 12, label: '十二' },{ value: 13, label: '十三' },{ value: 14, label: '十四' },{ value: 15, label: '十五' },{ value: 16, label: '十六' },{ value: 17, label: '十七' },{ value: 18, label: '十八' },{ value: 19, label: '十九' },{ value: 20, label: '二十' },{ value: 21, label: '二十一' },{ value: 22, label: '二十二' },{ value: 23, label: '二十三' },{ value: 24, label: '二十四' },{ value: 25, label: '二十五' },{ value: 26, label: '二十六' },{ value: 27, label: '二十七' },{ value: 28, label: '二十八' },{ value: 29, label: '二十九' },{ value: 30, label: '三十' },{ value: 31, label: '三十一' },{ value: 32, label: '三十二' },{ value: 33, label: '三十三' },{ value: 34, label: '三十四' },{ value: 35, label: '三十五' },{ value: 36, label: '三十六' },{ value: 37, label: '三十七' },{ value: 38, label: '三十八' },{ value: 39, label: '三十九' },{ value: 40, label: '四十' },{ value: 41, label: '四十一' },{ value: 42, label: '四十二' },{ value: 43, label: '四十三' },{ value: 44, label: '四十四' },{ value: 45, label: '四十五' },{ value: 46, label: '四十六' },{ value: 47, label: '四十七' },{ value: 48, label: '四十八' },{ value: 49, label: '四十九' },{ value: 50, label: '五十' },{ value: 51, label: '五十一' },{ value: 52, label: '五十二' },{ value: 53, label: '五十三' },{ value: 54, label: '五十四' },{ value: 55, label: '五十五' },{ value: 56, label: '五十六' },{ value: 57, label: '五十七' },{ value: 58, label: '五十八' },{ value: 59, label: '五十九' },{ value: 60, label: '六十' }]
var thisMonth = moment().format('MM');
createEle('.month', thisMonth, 12, 180);
var thisDays = moment().format('DD');
let maxDays = moment().month(moment().month()).endOf('month').format("DD");
createEle('.days', thisDays, maxDays, 230);
var thisHour = moment().format('HH');
createEle('.hours', thisHour, 24, 300);
/**
* el:盒子元素
* activeIndex: 当前时间(月、日、时、分、秒)
* length: 长度(12月、当前月多少天、24小时、60分、60秒)
* r: 半径
* cb: 回调函数
*/
function createEle(el, activeIndex, length, r, cb) {
let list = CN.slice(0, length);
let thisIndex = 0;
list.forEach((item, index)=>{
if(item.value == activeIndex){
thisIndex = index;
}
})
var a = list.slice(0, thisIndex);
var b = list.slice(thisIndex);
console.log(b.concat(a));
b.concat(a).forEach(item=>{
$(el).append('<div class="box">'+item.label+'</div>')
})
circle(el, r, cb);
}
秒轮盘动画
上面的回调一直没有用,其实主要就是用来整个轮盘旋转动画的。
从秒开始,每秒旋转一下每个轮盘对应每个元素的角度。
var thisSecond = moment().format('ss');
createEle('.second', thisSecond, 60, 450, (avd)=>{
setInterval(()=>{
let transform = $('.second').css('transform');
if(transform){
// 获取当前元素已经旋转的角度,然后加上每个元素应该分的角度
let rotate = getTranslate(transform, 'rotate');
$('.second').css({
'transform': 'rotate('+(rotate + avd)+'deg)',
'transition': 'all 1s linear'
})
}
}, 1000)
});
下面方法,用来获取元素transform的值,可以定义参数,分别获取x、y、z以及rotate的值。
/**
* transform: 元素的transform(rotate(100deg))
* sty: 字符串,获取transform的对应的具体值(x、y、z以及rotate)
*/
function getTranslate(transform,sty){//获取transform值
var translates=transform.substring(7);
var result = translates.match(/\(([^)]*)\)/);// 正则()内容
var matrix=result?result[1].split(','):translates.split(',');
if(sty=="x" || sty==undefined){
return matrix.length>6?parseFloat(matrix[12]):parseFloat(matrix[4]);
}else if(sty=="y"){
return matrix.length>6?parseFloat(matrix[13]):parseFloat(matrix[5]);
}else if(sty=="z"){
return matrix.length>6?parseFloat(matrix[14]):0;
}else if(sty=="rotate"){
return matrix.length>6?getRotate([parseFloat(matrix[0]),parseFloat(matrix[1]),parseFloat(matrix[4]),parseFloat(matrix[5])]):getRotate(matrix);
}
}
function getRotate(matrix){
var aa=Math.round(180*Math.asin(matrix[0])/ Math.PI);
var bb=Math.round(180*Math.acos(matrix[1])/ Math.PI);
var cc=Math.round(180*Math.asin(matrix[2])/ Math.PI);
var dd=Math.round(180*Math.acos(matrix[3])/ Math.PI);
var deg=0;
if(aa==bb||-aa==bb){
deg=dd;
}else if(-aa+bb==180){
deg=180+cc;
}else if(aa+bb==180){
deg=360-cc||360-dd;
}
return deg>=360?0:deg;
}
这样一来,就有了轮盘转动效果
目前就实现了秒轮盘转动,如果想要其他都遵循时间,其实也不难,只需要从秒开始,到60,分就选中一次。分到60,小时就旋转一次。小时到24,日就旋转一次,一次类推。就是实现整个效果。