🤩原生JS实现一个翻页时钟

·  阅读 6572
🤩原生JS实现一个翻页时钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁

  • 最近很多人在考研备战,因为经常去考研自习室找男朋友,感觉很多人平板都放着一个翻页时间显示器,看到之后就在想怎么实现比较好哈哈
  • 那么我们也来练手练手,用久了Vue,再来写CSS和JS操纵DOM多少都有点生疏哈哈哈

一、基本结构

  • 在一开始肯定还是先打出我们的基本结构
  • 主要是一个显示页,然后分别显示,时,分,秒 image.png
  • 最外面由一个show包围着,里面包裹一个类名为time的ul盒子,然后依次放上li盒子即可
.show {
  width: 800px;
  height: 300px;
  margin: auto;
  background-color: black;
  color: #fff;
}
.time {
  display: flex;
  flex: 1;
  font-size: 160px;
  text-align: center;
  line-height: 300px;
  padding: 0 20px;
  overflow: hidden;
}
复制代码
  • 因为我们希望中间:的不要那么占地方,同时要给数字多设样式,刚好数字占用奇数li冒号占用偶数li,那么给他分别设置即可
.time li:nth-child(2n) {
  flex: 1;
}
.time li:nth-child(2n+1) {
  flex: 4;
  background-color: #3b3d3b;
  position: relative;
  line-height: 200px;
  height:200px;
  margin: auto 20px;
  border-radius: 10px;
}
复制代码

二、日历样式

  • 我们说要设置日历,那么就需要把数字分为上下两部分,就在里面放入两个盒子

一开始我用的是伪元素法::before::after,后面发现用JS去操控伪元素修改数据和操作动画都太麻烦了又不优雅.......,所以就换成了直接放两个盒子算了

  • 我们这里为了方便观看将上下两部分设置成了两个颜色(随机设的还挺好看)
  • 对上部分的元素设置top:0, bottom:50%
  • 对下部分的元素设置top:50%,bottom:0
  • 再对上部分设置了下边框: border-bottom: solid 3px #3b3d3b;,主要是想要让上下两部分有间隔感
.time li:nth-child(2n+1) .upBox,
.time li:nth-child(2n+1) .downBox {
  position: absolute;
  left: 0;
  right: 0;
  overflow: hidden;
}
.time li:nth-child(2n+1) .upBox {
  top: 0;
  bottom: 50%;
  box-sizing: border-box;
  background-color: pink;
  border-bottom: solid 3px #3b3d3b;
}
.time li:nth-child(2n+1) .downBox {
  top: 50%;
  bottom: 0;
  line-height: 0;
  box-sizing: border-box;
  background-color: rgb(212, 248, 158);
  overflow: hidden;
  transform-origin: top;
}
复制代码

image.png

三、添加文字

  • 经过上面的一番操作,我们把文字给覆盖掉了,所以就是说,数字还需要再额外处理一下让它能够分别显示在上下两部分

  • 直接给盒子内容加上数字--无疑是非常奇怪 image.png

  • 我们可以给下部分的设置line-height:0

  • 为什么要设置line-height:0呢,我们来个测试就知道了,左边为没有加之前,右边为加之后,可以知道加上line-height:0后刚好可以让文字上移一半,那么下面那一半刚好就是我们想要的

    image.pngimage.png

  • 同样的道理,给他应用到我们这里,就可以实现出我们期待的效果了 image.png

四、翻转

  • 给盒子设置rotateX让他翻转180°,然后设置persective让它的效果更加逼真
@keyframes go {
 0%{
   transform: perspective(500px)  rotateX(0deg);
 }
 100%{
   transform: perspective(500px)  rotateX(180deg);
 }
}
复制代码

翻转.gif

当然这个步骤只是我们用来判断动画效果的而已,真正实施还是需要用JS来操纵动画

六、盒子结构

  • 既然是日历翻转样式,那么它就不会仅有一页,而是有多页去切换,当然,我们这里可以让他有60个层叠的盒子,然后依次修改z-index就好,但是这样处理的话太粗鲁了
  • 我们选择让它两个盒子重叠(其实一共是四个盒子,但是层叠是两层),然后定时器的方式不断修改两个盒子的时间
  • html结构如下
 <li>
            <div class="upBox beforeTime"></div>
            <div class="downBox beforeTime"></div>
            <div class="upBox afterTime"></div>
            <div class="downBox afterTime"></div>
 </li>
复制代码

五、分析动画过程

  • 我们这里用侧面图
  • 一开始就是就是两个盒子层叠在一起,前面黄色盒子为旧时间,后面新盒子赋予最新时间,后面上面的那个以bottom为旋转轴,然后设置270deg
  • 然后前面下面辣个盒子以top为旋转轴,向0-90翻转,转到90°的时候设置它的z-index让它去最底层
  • 我本来是想让他180°旋转的,但是旋转之后它的数字会变成镜像的,所以我们选择让他到90°后隐藏,由后面上面的盒子完成90°-180°的旋转
    • 图为 秒时刻‘44’翻转后导致的 镜像结果 image.png
    • 图为侧面图
      image.pngimage.pngimage.png
  • 因为前面上面的盒子在选择过程中如果有露出来的话应该始终是旧的时间直到新的时间去覆盖它,然后设置它为新时间,我这里是选择让它在150°的时候更改为最新时间

五、设置时间

  • 要设置时间首先就要先获取时间
  • JS提供了API可以让我们直接获取到数据
    • 通过getHours()获取时
    • 通过getMinutes()获取分钟
    • 通过getSeconds() 获取秒
var time = new Date();
var hour = time.getHours();
var minute = time.getMinutes();
var second = time.getSeconds();
console.log(hour, minute, second); //23 35 14
//表示 23时,35分,14秒
复制代码
  • 但是这样直接获取出来的时间是有点问题的,比如15:03:04,获取出来的时,分,秒分别为15,3,4;我们更希望时间都是两位数,所以这里还需要给他处理一下
//格式化时间
const formatTime = (time)=>{
  if(time < 10) time = '0' + time
  return time
}
复制代码
  • 得到想要的时间之后需要给盒子服时间初始值,于是就使用innerHTML给他放到合适的地方,因为这里类名为beforeTime的都有两个盒子,所以我加上了forEach循环
document.querySelector('li:nth-child(1)').querySelectorAll('.beforeTime').forEach(ele=> ele.innerHTML = formatTime(oldHour));
document.querySelector('li:nth-child(3)').querySelectorAll('.beforeTime').forEach(ele=> ele.innerHTML = formatTime(oldMinute));
document.querySelector('li:nth-child(5)').querySelectorAll('.beforeTime').forEach(ele=> ele.innerHTML = formatTime(oldSecond));
复制代码
  • 因为我们的时间是不断更新的,所以还需要用上setTimeout后,这里要记住前面分析动画的时候说了前面盒子为旧时间,后面盒子为新时间,所以setTimeout里面应该是给后面的盒子赋值
const changeTime = ()=>{
    let time = new Date();
    let hour = time.getHours();
    let minute = time.getMinutes();
    let second = time.getSeconds();
    document.querySelector(' li:nth-child(1)').querySelectorAll('.afterTime').forEach(ele=> ele.innerHTML = formatTime(hour));
    document.querySelector(' li:nth-child(3)').querySelectorAll('.afterTime').forEach(ele=> ele.innerHTML = formatTime(minute));
    document.querySelector(' li:nth-child(5)').querySelectorAll('.afterTime').forEach(ele=> ele.innerHTML = formatTime(second));
    setTimeout(changeTime,1000);
}
changeTime();
复制代码
  • 这里还可以给他优化一下,因为时和分是比较久才需要重新赋值一次,我们不希望每一秒就给每个盒子使用一次innerHTML,那么就可以拿盒子的值和最新时间进行比较,如果相等的话就不需要去进行遍历修改,不过秒就不用判断了,反正它每次都要变化
    const setHourBox = document.querySelector('li:nth-child(1)').querySelectorAll('.afterTime');
    if(!setHourBox[0].innerHTML || setHourBox[0].innerHTML!=formatTime(hour)) {
      document.querySelector(' li:nth-child(1)').querySelectorAll('.afterTime').forEach(ele=> ele.innerHTML = formatTime(hour));
    }
    const setMinuteBox = document.querySelector('li:nth-child(3)').querySelectorAll('.afterTime');
    if(!setMinuteBox[0].innerHTML || setMinuteBox[0].innerHTML != formatTime(minute)) {
      document.querySelector(' li:nth-child(3)').querySelectorAll('.afterTime').forEach(ele=> ele.innerHTML = formatTime(minute));
      console.log('1123')
    }
    document.querySelector(' li:nth-child(5)').querySelectorAll('.afterTime').forEach(ele=> ele.innerHTML = formatTime(second));
复制代码

六、 设置动画

  • 使用了闭包的性质,让动画开始前先给它设置一些属性
  • 这里每次动画是选择了使用requestAnimation,具体原因可以看看 说说requestAnimationFrame吧 - 掘金 (juejin.cn)
  • 因为浏览器是60HZ,即16.7ms左右执行一次,那么我们需要设置每次让它增加3deg,差不多一秒左右可以完成180deg的旋转
  • 然后按照我们上面分析的旋转过程进行设置即可,中间出现的rotateDown函数我们就不赘述啦,反正就是让它从270到360°旋转而已
//翻转前面下面的盒子向上180deg
const rotateUp = (ele,time, n) => {
  //传入的为一开始翻转的元素,即前面下面的盒子,以及新时间,以及第几个li盒子
  let rotateDeg = 0;
  ele.style.zIndex=50;
  //这个是所有上面的盒子
  const allUpBox =  document.querySelector(`li:nth-child(${n})`).querySelectorAll('.upBox');
  //所有前面的盒子
  const beforeTime = document.querySelector(`li:nth-child(${n})`).querySelectorAll('.beforeTime');
  // 让上面后面的盒子先不可见,然后设置为270°
  allUpBox[1].style.display = 'none';
  allUpBox[1].transform = `rotateX(270deg)`;
  const animation = () => {
    rotateDeg+=3;
    ele.style.transform = `perspective(160px) rotateX(${rotateDeg}deg)`;
    if(rotateDeg == 90) {
      //让它更新为最近时间后隐藏
      ele.innerHTML = time
      ele.style.zIndex = -1;
      //让刚刚上面隐藏的盒子重新显示出来并且完成90°-180°的旋转
      allUpBox[1].style.display = 'block';
      allUpBox[0].style.zIndex = 1;
      rotateDown(allUpBox[1])
      allUpBox[1].style.zIndex = 1;
    }
    if(rotateDeg == 150) {
      beforeTime[0].innerHTML = time
    }
    if(rotateDeg < 180) {
      requestAnimationFrame(animation);
    }
  }
  animation()
} 
复制代码
  • 设置完动画的之后我们只需要绑定给元素即可,绑定的时间是在我们重新设置时间的时候,所以这里如果是第一次设置时间的话记得给他加个判断,不要给他绑定动画,不然你就会面对一刷新就是三个盒子同时翻转(是有点好笑哈哈哈哈)
    const setHourBox = document.querySelector('li:nth-child(1)').querySelectorAll('.afterTime');
    if(!setHourBox[0].innerHTML || setHourBox[0].innerHTML!=formatTime(hour)) {
      if(setHourBox[0].innerHTML)  rotateUp(document.querySelector('li:nth-child(1)').querySelectorAll('.beforeTime')[1], formatTime(hour), 1);
      setHourBox.forEach(ele=> ele.innerHTML = formatTime(hour));
    }
    const setMinuteBox = document.querySelector('li:nth-child(3)').querySelectorAll('.afterTime');
    if(!setMinuteBox[0].innerHTML || setMinuteBox[0].innerHTML != formatTime(minute)) {
      if(setMinuteBox.innerHTML)  rotateUp(document.querySelector('li:nth-child(3)').querySelectorAll('.beforeTime')[1], formatTime(minute), 3);
      setMinuteBox.forEach(ele=> ele.innerHTML = formatTime(minute));
    }
    const setSecondBox = document.querySelector('li:nth-child(5)').querySelectorAll('.afterTime')
    rotateUp(document.querySelector('li:nth-child(5)').querySelectorAll('.beforeTime')[1], formatTime(second), 5)
    setSecondBox.forEach(ele=> ele.innerHTML = formatTime(second));
复制代码

七、样式优化

  • 完成到这里之后,它就已经是这样了

时钟动起来.gif

  • 但是花花绿绿的哈哈哈,怪没质感的
  • 所以把前面的为了方便看的粉色绿色去掉,加上阴影box-shadow: 0 0 20px 3px white;,让他更加有质感一点
  • 这里还微调了一下,全部代码已经放到码上掘金啦

八、码上掘金

到这里就都完成啦, 觉得还不错的点个赞吧🥰

收藏成功!
已添加到「」, 点击更改