微信小程序实战:打造一个带闹钟功能的模拟时钟

在移动应用开发中,时钟和闹钟是非常常见的功能模块。本文将详细介绍如何使用微信小程序开发一个兼具模拟时钟显示和闹钟功能的应用,包含完整的代码实现和功能解析。
在这里插入图片描述

功能概述
这个小程序实现了以下核心功能:

  • 模拟传统钟表的指针式显示
  • 实时数字时间显示
  • 自定义闹钟时间设置
  • 闹钟开关控制
  • 铃声播放与控制

界面简洁直观,包含时钟表盘、时间显示区、闹钟设置区和控制开关,整体交互流畅。

界面主要分为两个部分:

1.时钟表盘区域:通过动态数据绑定实现了可旋转的刻度和指针
2.控制区域:包含当前时间显示、闹钟时间选择器和两个功能开关

页面加载与初始化(onLoad)
页面加载时主要完成以下工作:

  • 初始化钟表刻度
  • 初始化并开始更新时间
  • 创建音频上下文并设置事件监听

界面效果与交互

  • 时钟表盘采用圆形设计,带有清晰的小时和分钟刻度
  • 时针、分针、秒针以不同的速度平滑转动
  • 下方显示数字式时间
  • 可通过时间选择器设置闹钟时间
  • 两个开关分别控制闹钟是否启用和铃声播放状态
  • 当系统时间达到设定的闹钟时间且闹钟处于开启状态时,自动播放铃声

1、js代码:

Page({
  data: {
    hourDeg: 0,
    minuteDeg: 0,
    secondDeg: 0,
    timeString: '',
    hourMarks: [],
    minuteMarks: [],
    isClock: true,
    clock: '00:00',
    showClock: '00:00',
    isPlaying: false, // 记录是否正在播放
    innerAudioContext: null
  },

  /*
   * 页面初始化
   */
  onLoad() {
    // 初始化钟表刻度
    this.initClockMarks();
    // 初始化时间
    this.updateTime();
    // 每秒更新一次时间
    this.interval = setInterval(() => {
      this.updateTime();
    }, 1000);
    // 音频监听
    const innerAudioContext = wx.createInnerAudioContext();
    innerAudioContext.src = 'https://wsaudiobssdlbig.kugou.com/202507191305/1e5bcb88ca2ff79ab857cfa47fcc392a/bss/extname/wsaudio/c6a33db05c677c02cdca3428ee2e3470.mp3';
    this.setData({
      innerAudioContext
    });
    // 监听播放开始
    innerAudioContext.onPlay(() => {
      this.setData({
        isPlaying: true
      });
    });

    // 监听暂停
    innerAudioContext.onPause(() => {
      this.setData({
        isPlaying: false
      });
    });

    // 监听播放结束(自动停止)
    innerAudioContext.onEnded(() => {
      this.setData({
        isPlaying: false
      });
    });

    // 监听停止
    innerAudioContext.onStop(() => {
      this.setData({
        isPlaying: false
      });
    });

    // 监听错误(播放失败时)
    innerAudioContext.onError(() => {
      this.setData({
        isPlaying: false
      });
    });
  },
  // 开关切换
  toggleSwitch(e) {
    let obj = e.currentTarget.dataset.key;
    let {
      isClock,
      isPlaying,
      innerAudioContext
    } = this.data;
    if (obj == 'clock') {
      let tag = !isClock;
      this.setData({
        isClock: tag,
        clock: !tag ? '设置闹铃' : '00:00',
        showClock: !tag ? '设置闹铃' : '00:00'
      });
      if(!tag){
        this.setData({
          isPlaying: false
        });
        innerAudioContext.pause();
      }
    }
    if (obj == 'play') {
      if (isPlaying) {
        innerAudioContext.pause();
      } else {
        innerAudioContext.play();
      }
    }
  },
  bindClockChange: function (e) {
    let val = e.detail.value;
    let str = (val.split(":"));
    let attr = {
      12: '00',
      13: '01',
      14: '02',
      15: '03',
      16: '04',
      17: '05',
      18: '06',
      19: '07',
      20: '08',
      21: '09',
      22: '10',
      23: '11',
      24: '00'
    };
    let val2 = (str[0] > 11 ? attr[str[0]] : val)+':'+str[1];
    this.setData({
      clock: val2,
      showClock: val,
      isClock: true
    })
  },
  initClockMarks() {
    // 初始化小时刻度
    const hourMarks = [];
    const json = {
      0: 12,
      90: 3,
      180: 6,
      270: 9
    };
    for (let i = 0; i < 12; i++) {
      let mark = i * 30;
      hourMarks.push({
        deg: mark,
        digit: json[mark] || 0
      });
    }

    // 初始化分钟刻度(排除与小时刻度重合的)
    const minuteMarks = [];
    for (let i = 0; i < 60; i++) {
      if (i % 5 !== 0) { // 排除整点位置
        minuteMarks.push({
          deg: i * 6
        });
      }
    }

    this.setData({
      hourMarks,
      minuteMarks
    });
  },
  /*
   * 更新时间
   */
  updateTime() {
    const now = new Date();
    const hours = now.getHours() % 12;
    const minutes = now.getMinutes();
    const seconds = now.getSeconds();

    // 计算各指针角度
    const hourDeg = hours * 30 + minutes * 0.5;
    const minuteDeg = minutes * 6 + seconds * 0.1;
    const secondDeg = seconds * 6;

    // 格式化数字时间
    let hour = hours.toString().padStart(2, '0');
    let minu = minutes.toString().padStart(2, '0');
    const timeString = [
      hour,
      minu,
      seconds.toString().padStart(2, '0')
    ].join(':');

    this.setData({
      hourDeg,
      minuteDeg,
      secondDeg,
      timeString
    });
    let time = hour + ':' + minu;
    let {
      clock,
      isPlaying,
      isClock,
      innerAudioContext
    } = this.data;
    if (time == clock && !isPlaying && isClock) {
      innerAudioContext.play();
    }
  },
  /*
   * 页面卸载时清除定时器
   */
  onUnload() {
    clearInterval(this.interval);
    this.data.innerAudioContext.destroy();
  },
})

2、wxml代码:

<view class="box">
  <view class="clock-box">
    <!-- 钟表刻度 -->
    <view class="clock-dial">
      <view class="hour-mark" wx:for="{{hourMarks}}" wx:for-item="item" wx:for-index="index" wx:key="item" style="transform: rotate({{item.deg}}deg)">
        <view class="hour-digit deg-{{item.digit}}" hidden="{{item.digit<1}}">{{item.digit}}</view>
      </view>
      <view class="minute-mark" wx:for="{{minuteMarks}}" wx:for-item="item" wx:for-index="index" wx:key="item" style="transform: rotate({{item.deg}}deg)"></view>
      <!-- 钟表指针 -->
      <view class="hour" style="transform: rotate({{hourDeg}}deg)"></view>
      <view class="minute" style="transform: rotate({{minuteDeg}}deg)"></view>
      <view class="second" style="transform: rotate({{secondDeg}}deg)"></view>
      <!-- 钟表中心 -->
      <view class="clock-center"></view>
    </view>
  </view>
</view>
<view class="end-box">
  <view class="row">
    <view class="left">当前时间</view>
    <view class="right">{{timeString}}</view>
  </view>
  <view class="row">
    <view class="left">响铃时间</view>
    <picker class="right" mode="time" value="{{showClock}}" start="09:01" end="21:01" bindchange="bindClockChange">{{showClock}}</picker>
  </view>
  <view class="row">
    <view class="left">开启闹钟</view>
    <view class="right">
      <view class="custom-switch {{isClock ? 'switch-on' : 'switch-off'}}" bindtap="toggleSwitch" data-key="clock">
        <view class="switch-dot" style="transform: translateX({{isClock ? 60 : 5}}rpx);"></view>
      </view>
    </view>
  </view>
  <view class="row">
    <view class="left">播放铃声</view>
    <view class="right">
      <view class="custom-switch {{isPlaying ? 'switch-on' : 'switch-off'}}" bindtap="toggleSwitch" data-key="play">
        <view class="switch-dot" style="transform: translateX({{isPlaying ? 60 : 5}}rpx);"></view>
      </view>
    </view>
  </view>
</view>

3、wxss代码:

page {
  background-color: white;
}

.box {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #f0f2f5;
  padding: 6% 0;
}

.clock-box {
  text-align: center;
}

.clock-dial {
  position: relative;
  width: 580rpx;
  height: 580rpx;
  border-radius: 50%;
  border: 20rpx double #A9D4D3;
  background-color: #fff;
  box-shadow: 0 0 50rpx rgba(0, 0, 0, 0.1);
}

.hour-mark {
  position: absolute;
  top: 10rpx;
  left: 50%;
  width: 5rpx;
  height: 30rpx;
  background-color: #333;
  transform-origin: 50% 280rpx;
}

.hour-digit {
  position: absolute;
  top: 30rpx;
  font-size: 34rpx;
  font-weight: bold;
}

.deg-3 {
  left: -10rpx;
  transform: rotate(-90deg);
}

.deg-6 {
  left: -8rpx;
  transform: rotate(180deg);
}

.deg-9 {
  left: -8rpx;
  transform: rotate(90deg);
}

.deg-12 {
  left: -22rpx;
}

.minute-mark {
  position: absolute;
  top: 10rpx;
  left: 50%;
  width: 2rpx;
  height: 16rpx;
  background-color: #999;
  transform-origin: 50% 280rpx;
}

.hour {
  position: absolute;
  bottom: 50%;
  left: 50%;
  width: 12rpx;
  height: 120rpx;
  background-color: #333;
  border-radius: 10rpx;
  transform-origin: 50% 100%;
  margin-left: -8rpx;
}

.minute {
  position: absolute;
  bottom: 50%;
  left: 50%;
  width: 10rpx;
  height: 160rpx;
  background-color: #666;
  border-radius: 10rpx;
  transform-origin: 50% 100%;
  margin-left: -5rpx;
}

.second {
  position: absolute;
  bottom: 50%;
  left: 50%;
  width: 4rpx;
  height: 220rpx;
  background-color: #f88a04;
  border-radius: 10rpx;
  transform-origin: 50% 100%;
  margin-left: -2rpx;
}

.clock-center {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 24rpx;
  height: 24rpx;
  background-color: #333;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  z-index: 10;
}

/* 底部按钮 */
.end-box {
  margin-top: 20rpx;
  background-color: #f0f2f5;
}

.row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 36rpx 20rpx;
  font-size: 30rpx;
  border-bottom: 1rpx solid #c9e7e6;
}

.left {
  color: rgb(59, 59, 59);
}

.right {
  color: rgb(68, 67, 67);
}

/* 自定义开关 */
.custom-switch {
  position: relative;
  width: 100rpx;
  height: 40rpx;
  border-radius: 50rpx;
  transition: background-color 0.2s;
  box-sizing: border-box;
}

.switch-on {
  background-color: #00CED1;
}

.switch-off {
  background-color: #e5e5e5;
}

.switch-dot {
  position: absolute;
  top: 6rpx;
  left: 5rpx;
  height: 28rpx;
  width: 28rpx;
  border-radius: 50%;
  background-color: #fff;
  box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.4);
  transition: transform 0.2s;
}

4、json代码:

{
  "usingComponents": {},
  "navigationBarTextStyle": "white",
  "navigationBarBackgroundColor": "#00CED1",
  "navigationBarTitleText": "时钟小工具"
}

总结与扩展
这个小程序实现了一个功能完整的时钟闹钟应用,代码结构清晰,注释完善,主要特点:

  • 采用数据驱动的方式实现了动态时钟效果
  • 精确的指针角度计算确保了时钟显示的准确性
  • 完整的音频控制逻辑实现了铃声播放功能
  • 简洁直观的用户界面和交互设计

可以进一步扩展的功能:

  • 添加多个闹钟设置
  • 提供多种铃声选择
  • 增加闹钟重复设置(工作日、周末等)
  • 添加倒计时功能
  • 优化 UI 设计,增加主题切换

通过这个实例,我们可以学习到微信小程序中数据绑定、事件处理、定时器使用、音频控制等多个方面的知识,希望对大家的小程序开发有所帮助!

更多示例,请关注 蒜鸟编程(微信小程序 / 抖音 / 小红书/微信公众号/微博****等同名称),在这里,编程与生活的边界从未如此清晰,期待与大家共同成长!后续将围绕编程技术持续更新更多实践示例,如果在实现过程中遇到问题,或者有更好的优化建议,欢迎在评论区交流讨论!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐