时钟翻牌器 vue3

在这里插入图片描述

在这里插入图片描述

index.vue

<template>
  <div class="FlipClock">
    <Flipper ref="flipperHour" />
    <em>:</em>
    <Flipper ref="flipperMinute" />
    <em>:</em>
    <Flipper ref="flipperSecond" />
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue"
import Flipper from "./Flipper.vue"
const timer = ref<any>(null)
const flipObjs = ref<any[]>([])
const flipperHour = ref()
const flipperMinute = ref()
const flipperSecond = ref()
// 初始化数字
function init() {
  let now = new Date()
  let nowTimeStr = formatDate(new Date(now.getTime()), "hh-ii-ss")
  nowTimeStr = nowTimeStr.split("-")
  for (let i = 0; i < flipObjs.value.length; i++) {
    flipObjs.value[i].value.setFront(nowTimeStr[i])
  }
}
// 开始计时
function run() {
  timer.value = setInterval(() => {
    // 获取当前时间
    let now = new Date()
    let nowTimeStr = formatDate(new Date(now.getTime() - 1000), "hh-ii-ss")
    nowTimeStr = nowTimeStr.split("-")
    let nextTimeStr = formatDate(now, "hh-ii-ss")
    nextTimeStr = nextTimeStr.split("-")
    for (let i = 0; i < flipObjs.value.length; i++) {
      if (nowTimeStr[i] === nextTimeStr[i]) {
        continue
      }
      flipObjs.value[i].value.flipDown(nowTimeStr[i], nextTimeStr[i])
    }
  }, 1000)
}
// 正则格式化日期
function formatDate(date, dateFormat) {
  /* 单独格式化年份,根据y的字符数量输出年份
     * 例如:yyyy => 2019
            yy => 19
            y => 9
     */
  if (/(y+)/.test(dateFormat)) {
    dateFormat = dateFormat.replace(
      RegExp.$1,
      (date.getFullYear() + "").substr(4 - RegExp.$1.length)
    )
  }
  // 格式化月、日、时、分、秒
  let o = {
    "m+": date.getMonth() + 1,
    "d+": date.getDate(),
    "h+": date.getHours(),
    "i+": date.getMinutes(),
    "s+": date.getSeconds()
  }
  for (let k in o) {
    if (new RegExp(`(${k})`).test(dateFormat)) {
      // 取出对应的值
      let str = o[k] + ""
      /* 根据设置的格式,输出对应的字符
       * 例如: 早上8时,hh => 08,h => 8
       * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
       * 例如: 下午15时,hh => 15, h => 15
       */
      dateFormat = dateFormat.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
    }
  }
  return dateFormat
}
// 日期时间补零
function padLeftZero(str) {
  return str.toString().padStart(2, "0")
}
onMounted(() => {
  flipObjs.value = [flipperHour, flipperMinute, flipperSecond]
  init()
  run()
})
</script>

<style lang="scss" scoped>
.FlipClock {
  text-align: center;
}
.FlipClock .M-Flipper {
  margin: 0 3px;
}
.FlipClock em {
  display: inline-block;
  line-height: 102px;
  font-size: 66px;
  font-style: normal;
  vertical-align: top;
}
</style>

Flipper.vue

<template>
  <div class="M-Flipper" :class="[flipType, { go: isFlipping }]">
    <div class="digital front" :class="_textClass(frontTextFromData)"></div>
    <div class="digital back" :class="_textClass(backTextFromData)"></div>
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue"

const props = defineProps({
  // front paper text
  // 前牌文字
  frontText: {
    type: [Number, String],
    default: "00"
  },
  // back paper text
  // 后牌文字
  backText: {
    type: [Number, String],
    default: "01"
  },
  // flipping duration, please be consistent with the CSS animation-duration value.
  // 翻牌动画时间,与CSS中设置的animation-duration保持一致
  duration: {
    type: Number,
    default: 600
  }
})
const isFlipping = ref(false)
const flipType = ref("down")
const frontTextFromData = ref(props.frontText)
const backTextFromData = ref(props.backText)

function _textClass(number) {
  return "number" + number
}
function _flip(type, front, back) {
  // 如果处于翻转中,则不执行
  if (isFlipping.value) {
    return false
  }
  frontTextFromData.value = front
  backTextFromData.value = back
  // 根据传递过来的type设置翻转方向
  flipType.value = type
  // 设置翻转状态为true
  isFlipping.value = true
  setTimeout(() => {
    // 设置翻转状态为false
    isFlipping.value = false
    frontTextFromData.value = back
  }, props.duration)
}
// 下翻牌
function flipDown(front, back) {
  _flip("down", front, back)
}
// 上翻牌
function flipUp(front, back) {
  _flip("up", front, back)
}
// 设置前牌文字
function setFront(text) {
  frontTextFromData.value = text
}
// 设置后牌文字
function setBack(text) {
  backTextFromData.value = text
}
defineExpose({
  flipDown,
  flipUp,
  setFront,
  setBack
})
</script>

<style lang="scss" scoped>
.M-Flipper {
  display: inline-block;
  position: relative;
  width: 120px;
  height: 100px;
  line-height: 100px;
  border: solid 1px #000;
  border-radius: 10px;
  background: #fff;
  font-size: 66px;
  color: #fff;
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
  text-align: center;
  font-family: "Helvetica Neue";
}

.M-Flipper .digital:before,
.M-Flipper .digital:after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  background: #000;
  overflow: hidden;
  box-sizing: border-box;
}

.M-Flipper .digital:before {
  top: 0;
  bottom: 50%;
  border-radius: 10px 10px 0 0;
  border-bottom: solid 1px #666;
}

.M-Flipper .digital:after {
  top: 50%;
  bottom: 0;
  border-radius: 0 0 10px 10px;
  line-height: 0;
}

/*向下翻*/
.M-Flipper.down .front:before {
  z-index: 3;
}

.M-Flipper.down .back:after {
  z-index: 2;
  transform-origin: 50% 0%;
  transform: perspective(160px) rotateX(180deg);
}

.M-Flipper.down .front:after,
.M-Flipper.down .back:before {
  z-index: 1;
}

.M-Flipper.down.go .front:before {
  transform-origin: 50% 100%;
  animation: frontFlipDown 0.6s ease-in-out both;
  box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
  backface-visibility: hidden;
}

.M-Flipper.down.go .back:after {
  animation: backFlipDown 0.6s ease-in-out both;
}

/*向上翻*/
.M-Flipper.up .front:after {
  z-index: 3;
}

.M-Flipper.up .back:before {
  z-index: 2;
  transform-origin: 50% 100%;
  transform: perspective(160px) rotateX(-180deg);
}

.M-Flipper.up .front:before,
.M-Flipper.up .back:after {
  z-index: 1;
}

.M-Flipper.up.go .front:after {
  transform-origin: 50% 0;
  animation: frontFlipUp 0.6s ease-in-out both;
  box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
  backface-visibility: hidden;
}

.M-Flipper.up.go .back:before {
  animation: backFlipUp 0.6s ease-in-out both;
}

@keyframes frontFlipDown {
  0% {
    transform: perspective(160px) rotateX(0deg);
  }

  100% {
    transform: perspective(160px) rotateX(-180deg);
  }
}

@keyframes backFlipDown {
  0% {
    transform: perspective(160px) rotateX(180deg);
  }

  100% {
    transform: perspective(160px) rotateX(0deg);
  }
}

@keyframes frontFlipUp {
  0% {
    transform: perspective(160px) rotateX(0deg);
  }

  100% {
    transform: perspective(160px) rotateX(180deg);
  }
}

@keyframes backFlipUp {
  0% {
    transform: perspective(160px) rotateX(-180deg);
  }

  100% {
    transform: perspective(160px) rotateX(0deg);
  }
}
.M-Flipper {
  @for $i from 0 through 59 {
    $classPrefix: if($i <= 9, "number0", "number");
    $content: if($i <= 9, "0" + $i, $i);
    .digital.#{$classPrefix}#{$i}:before,
    .digital.#{$classPrefix}#{$i}:after {
      // 你的样式
      content: "#{$content}";
    }
  }
}
</style>

感谢大神_Nealyang_博客
感谢大神_Nealyang_Github

  • 9
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值