JavaScript時間輪盤:js元素圓形佈局製作時間輪盤動畫

lee_lgw發表於2021-09-09

前言

前段時間看抖音,有人用時間輪盤作為動態的桌面桌布,感覺很好玩,於是突發奇想,可以用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,沒有了解過的,可以先了解一波

年份輪盤

先收集今年後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,日就旋轉一次,一次類推。就是實現整個效果。

演示地址:
原始碼下載:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1020/viewspace-2822589/,如需轉載,請註明出處,否則將追究法律責任。

相關文章