Vue3项目中实现动态时钟的组件

目录

1、整体思路

2、实现过程

2.1、模板部分(Template)

2.2、脚本部分(Script)

2.3、样式部分(Style)

3、关键点解析

4、样式实现详解

4.1. 所需依赖

4.2. 基础容器

4.3. 数字排列(创新点)

4.4. 刻度生成

4.5. 指针动画(核心)

5、完整代码

6、最终实现效果


本文详细介绍了Vue 3项目中利用组件实现动态时钟效果的单文件组件,使用了<script setup>语法,并且样式部分使用了SCSS。

动态的模拟时钟,包括表盘、刻度、数字以及时针、分针和秒针。

先上效果如下:

1、整体思路

代码通过CSS动画实现时钟指针的连续旋转,使用Vue响应式变量存储当前时间,通过计算动画延迟使指针从当前时间位置开始旋转。

2、实现过程

2.1、模板部分(Template)

    • 有两个view,第一个显示“动态时钟”文字,第二个是时钟本身。

    • 时钟的容器(class为clock)通过绑定样式的方式,将秒、分、时的值作为CSS自定义属性(--ds, --dm, --dh)传递,用于后续的动画延迟计算。

    • 表盘上有12个数字(1-12),使用v-for循环生成,每个数字通过CSS的offset-path属性沿着圆形路径排列。

    • 有60个刻度,同样使用v-for生成,其中每5个刻度(5的倍数)会加粗显示,代表小时刻度。

    • 有时针、分针和秒针三个元素,它们分别通过CSS动画实现旋转。

    <template>
      <view class="clock-box">动态时钟</view>
      <view class="clock-box">
        <view class="clock" :style="{ '--ds': ds, '--dm': dm, '--dh': dh }">
          <view class="clock-pane">
            <text class="clock-num" :style="{ '--i': n }" v-for="n in 12" :key="n">{{ n }}</text>
          </view>
          <view class="clock-scales">
            <text class="clock-scale" :style="{ '--i': n }" v-for="n in 60" :key="n"></text>
          </view>
    
          <view class="clock-hour"></view>
          <view class="clock-min"></view>
          <view class="clock-sec"></view>
        </view>
      </view>
    </template>

    2.2、脚本部分(Script)

    • 使用setup语法,在组件初始化时获取当前时间,并计算出秒、分、时的值,但是注意,这里计算出的值用于初始设置动画的延迟。

    • 使用ref创建了响应式变量dsdmdh,分别代表秒、分、时(注意,分和时都包含了秒的小数部分,以便更精确地定位指针)。

    <script lang="ts" setup>
    const d = new Date();
    const h = d.getHours();
    const m = d.getMinutes();
    const s = d.getSeconds();
    const ds = ref(s);
    const dm = ref(m + s / 60);
    const dh = ref(h + m / 60 + s / 3600);
    </script>

    2.3、样式部分(Style)

    • 使用了SCSS语法,包含了一些嵌套规则和循环。

    • 时钟的容器设置了固定的宽高,圆角,阴影等。

    • 表盘(.clock-pane)和刻度容器(.clock-scales)绝对定位,并且刻度容器做了偏移调整。

    • 刻度的生成:通过SCSS的@for循环生成60个刻度,每个刻度旋转6度(360度/60)。

    • 数字的排列:使用CSS的offset-path属性,让12个数字沿着一个圆形路径(circle)排列,通过offset-distance调整每个数字的位置。

    • 指针的样式:分别设置了时针、分针和秒针的长度、颜色和宽度。它们都使用了同一个动画clock,但是动画的周期不同,并且通过动画延迟(animation-delay)来设置初始位置。

      • 时针动画周期为12小时(--step * 60 * 12),其中--step是60秒,即720分钟,也就是12小时。

      • 分针动画周期为60分钟(--step * 60),即1小时。

      • 秒针动画周期为60秒(--step),即1分钟。

    • 秒针的动画使用了steps(60),这样秒针会一跳一跳的,每分钟跳60次,即每秒一跳。

    • 动画延迟的计算:通过当前时间(时、分、秒)计算出动画应该开始的点,这样当时钟显示时,指针就能指向正确的时间。

    <style lang="scss">
    .clock-box {
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .clock {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 350px;
      height: 350px;
      font-size: 24px;
      border-radius: 20px;
      box-shadow: 2px 2px 20px #0000001a;
      --step: 60s;
    }
    
    .clock-pane {
      position: absolute;
      width: 250px;
      height: 250px;
      transform: translateX(0px);
    }
    
    .clock-scales {
      position: absolute;
      width: 250px;
      height: 250px;
      transform: translate(125px, -25px);
    }
    
    .clock-scale {
      position: absolute;
      top: 0;
      left: 0;
      width: 2px;
      height: 4px;
      background: #ccc;
      transform-origin: 0 150px;
    
      &:nth-child(5n + 1) {
        width: 4px;
        height: 6px;
        background: #333;
      }
    }
    
    @for $i from 1 through 60 {
      .clock-scale:nth-child(#{$i}) {
        transform: rotate(#{($i - 1) * 6deg});
      }
    }
    
    .clock-num {
      position: absolute;
      offset-path: path("M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z");
      offset-distance: calc(var(--i) * 10% / 1.2 - 25%);
      offset-rotate: 0deg;
    }
    
    .clock-hour {
      position: absolute;
      width: 4px;
      height: 60px;
      background: #333;
      transform: translateY(-50%) rotate(0);
      transform-origin: center bottom;
      animation: clock calc(var(--step) * 60 * 12) infinite linear;
      animation-delay: calc(-1 * var(--step) * var(--dh) * 60);
    }
    
    .clock-min {
      position: absolute;
      width: 4px;
      height: 90px;
      background: #333;
      transform: translateY(-50%) rotate(0);
      transform-origin: center bottom;
      animation: clock calc(var(--step) * 60) infinite linear;
      animation-delay: calc(-1 * var(--step) * var(--dm));
    }
    
    .clock-sec {
      position: absolute;
      width: 2px;
      height: 120px;
      background: red;
      transform: translateY(-50%) rotate(0);
      transform-origin: center bottom;
      animation: clock var(--step) infinite steps(60);
      animation-delay: calc(-1 * var(--step) * var(--ds) / 60);
    }
    
    .clock-sec::after {
      position: absolute;
      bottom: 0;
      left: 50%;
      width: 10px;
      height: 10px;
      content: "";
      background: #fff;
      border: 4px solid #333;
      border-radius: 50%;
      transform: translate(-50%, 50%);
    }
    
    @keyframes clock {
      to {
        transform: translateY(-50%) rotate(360deg);
      }
    }
    </style>
    

    3、关键点解析

    • 时针的动画周期是12小时,分针是1小时,秒针是1分钟。

    • 动画延迟的计算:

      秒针:-1 * var(--step) * var(--ds) / 60
      注意:--step是60秒,--ds是当前秒数(0-59),所以延迟是 -1 * 60s * 当前秒数 / 60 = -当前秒数 秒。
      例如,当前30秒,那么延迟就是-30秒,即动画从30秒前开始,现在就会指向30秒的位置。
      分针:-1 * var(--step) * var(--dm)
      --dm是当前分钟数(加上秒的小数部分,例如30.5分钟),所以延迟是 -1 * 60s * 当前分钟数 = -当前分钟数 分钟。
      时针:-1 * var(--step) * var(--dh) * 60
      --dh是当前小时数(加上分钟和秒的小数部分,例如2.5小时),乘以60将小时转换为分钟,再乘以60秒,得到秒数。

    这样,通过负的延迟,让动画从过去某个时间点开始,从而立即指向当前时间。

    4、样式实现详解

    4.1. 所需依赖

        "sass": "^1.67.0",
        "sass-loader": "^13.3.2",

    4.2. 基础容器

    .clock {
      width: 350px; height: 350px;
      --step: 60s; // CSS变量,定义动画基础周期
    }
    • 定义了正方形时钟容器

    • --step: 60s 是CSS自定义属性,作为动画计算的基础单位

    4.3. 数字排列(创新点)

    .clock-num {
      offset-path: path("M250 125c0 69.036..."); // 圆形路径
      offset-distance: calc(var(--i) * 10% / 1.2 - 25%);
    }

    使用CSS offset-path让数字沿圆形路径排列,比传统定位方式更优雅。

    4.4. 刻度生成

    // 生成60个刻度
    @for $i from 1 through 60 {
      .clock-scale:nth-child(#{$i}) {
        transform: rotate(#{($i - 1) * 6deg}); // 每个刻度旋转6度
      }
    }
    • 每个刻度旋转角度 = (序号-1) × 6°

    • 每5个刻度加粗显示(小时刻度)

    4.5. 指针动画(核心)

    .clock-sec {
      animation: clock var(--step) infinite steps(60);
      animation-delay: calc(-1 * var(--step) * var(--ds) / 60);
    }
    
    @keyframes clock {
      to {
        transform: translateY(-50%) rotate(360deg);
      }
    }

    动画逻辑

    • 周期--step (60秒) 旋转360°

    • 延迟-1 * 周期 * 当前秒数/60

      • 负延迟让动画从当前时间位置开始

      • 例如:当前30秒 → 延迟-30秒 → 从30秒位置开始

    三个指针的区别

    指针动画周期动画延迟步进方式
    秒针60秒calc(-1 * var(--step) * var(--ds) / 60)steps(60) 模拟跳秒
    分针60分钟calc(-1 * var(--step) * var(--dm))linear 平滑移动
    时针12小时calc(-1 * var(--step) * var(--dh) * 60)linear 平滑移动

    5、完整代码

    <template>
      <!-- 外层容器 -->
      <view class="clock-box">动态时钟</view>
      <view class="clock-box">
        <!-- 时钟主体,通过CSS变量传递时间值 -->
        <view class="clock" :style="{ '--ds': ds, '--dm': dm, '--dh': dh }">
          <!-- 12个数字 -->
          <view class="clock-pane">
            <text class="clock-num" :style="{ '--i': n }" v-for="n in 12" :key="n">{{ n }}</text>
          </view>
          <!-- 60个刻度 -->
          <view class="clock-scales">
            <text class="clock-scale" :style="{ '--i': n }" v-for="n in 60" :key="n"></text>
          </view>
    
          <!-- 时钟指针 -->
          <view class="clock-hour"></view>
          <!-- 时针 -->
          <view class="clock-min"></view>
          <!-- 分针 -->
          <view class="clock-sec"></view>
          <!-- 秒针 -->
        </view>
      </view>
    </template>
    
    <script lang="ts" setup>
    // 获取当前时间
    const d = new Date();
    const h = d.getHours(); // 小时 (0-23)
    const m = d.getMinutes(); // 分钟 (0-59)
    const s = d.getSeconds(); // 秒 (0-59)
    
    // 转换为响应式变量
    const ds = ref(s); // 秒数
    const dm = ref(m + s / 60); // 分钟 + 秒的小数部分
    const dh = ref(h + m / 60 + s / 3600); // 小时 + 分和秒的小数部分
    </script>
    
    <style lang="scss">
    .clock-box {
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .clock {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 350px;
      height: 350px;
      font-size: 24px;
      border-radius: 20px;
      box-shadow: 2px 2px 20px #0000001a;
      --step: 60s; // CSS变量,定义动画基础周期
    }
    
    .clock-pane {
      position: absolute;
      width: 250px;
      height: 250px;
      transform: translateX(0px);
    }
    
    .clock-scales {
      position: absolute;
      width: 250px;
      height: 250px;
      transform: translate(125px, -25px);
    }
    
    .clock-scale {
      position: absolute;
      top: 0;
      left: 0;
      width: 2px;
      height: 4px;
      background: #ccc;
      transform-origin: 0 150px;
    
      &:nth-child(5n + 1) {
        width: 4px;
        height: 6px;
        background: #333;
      }
    }
    // 生成60个刻度
    @for $i from 1 through 60 {
      .clock-scale:nth-child(#{$i}) {
        transform: rotate(#{($i - 1) * 6deg}); // 每个刻度旋转6度
      }
    }
    
    .clock-num {
      position: absolute;
      offset-path: path("M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z"); // 圆形路径
      offset-distance: calc(var(--i) * 10% / 1.2 - 25%);
      offset-rotate: 0deg;
    }
    
    .clock-hour {
      position: absolute;
      width: 4px;
      height: 60px;
      background: #333;
      transform: translateY(-50%) rotate(0);
      transform-origin: center bottom;
      animation: clock calc(var(--step) * 60 * 12) infinite linear;
      animation-delay: calc(-1 * var(--step) * var(--dh) * 60);
    }
    
    .clock-min {
      position: absolute;
      width: 4px;
      height: 90px;
      background: #333;
      transform: translateY(-50%) rotate(0);
      transform-origin: center bottom;
      animation: clock calc(var(--step) * 60) infinite linear;
      animation-delay: calc(-1 * var(--step) * var(--dm));
    }
    
    .clock-sec {
      position: absolute;
      width: 2px;
      height: 120px;
      background: red;
      transform: translateY(-50%) rotate(0);
      transform-origin: center bottom;
      animation: clock var(--step) infinite steps(60);
      animation-delay: calc(-1 * var(--step) * var(--ds) / 60);
    }
    
    .clock-sec::after {
      position: absolute;
      bottom: 0;
      left: 50%;
      width: 10px;
      height: 10px;
      content: "";
      background: #fff;
      border: 4px solid #333;
      border-radius: 50%;
      transform: translate(-50%, 50%);
    }
    
    @keyframes clock {
      to {
        transform: translateY(-50%) rotate(360deg);
      }
    }
    </style>
    

    6、最终实现效果

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    hhzz

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

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

    余额充值