微信小程序计时器开发实战:从零到一

发布时间:2026/10/14 17:19:06
微信小程序计时器开发实战:从零到一
项目结构先看一下整体的项目结构text├── app.js # 小程序入口 ├── app.json # 全局配置 ├── app.wxss # 全局样式 ├── components/ │ └── navigation-bar/ # 自定义导航栏组件 │ ├── navigation-bar.js │ ├── navigation-bar.json │ ├── navigation-bar.wxml │ └── navigation-bar.wxss ├── pages/ │ └── index/ │ ├── index.js # 计时器逻辑 │ ├── index.json │ ├── index.wxml │ └── index.wxss └── project.config.json一、全局配置app.jsonjson{ pages: [ pages/index/index ], window: { navigationBarTextStyle: black, navigationStyle: custom }, style: v2, renderer: webview, componentFramework: glass-easel, sitemapLocation: sitemap.json, lazyCodeLoading: requiredComponents }这里有几个关键配置navigationStyle: custom禁用系统默认导航栏这样我们才能使用自定义导航栏组件。lazyCodeLoading: requiredComponents按需注入组件代码优化启动性能。componentFramework: glass-easel使用新版组件框架。app.jsjavascript// app.js App({})入口文件很简单因为逻辑都写在页面里了。二、自定义导航栏组件由于禁用了系统导航栏我们需要自己实现一个。这里直接使用了一套现成的 weui 风格导航栏组件。组件注册 navigation-bar.jsonjson{ component: true, styleIsolation: apply-shared, usingComponents: {} }styleIsolation: apply-shared让组件的样式可以和应用样式共享。组件逻辑 navigation-bar.jsjavascriptComponent({ options: { multipleSlots: true // 启用多slot支持 }, properties: { extClass: { type: String, value: }, title: { type: String, value: }, background: { type: String, value: }, color: { type: String, value: }, back: { type: Boolean, value: true }, loading: { type: Boolean, value: false }, homeButton: { type: Boolean, value: false }, animated: { type: Boolean, value: true }, show: { type: Boolean, value: true, observer: _showChange }, delta: { type: Number, value: 1 }, }, data: { displayStyle: }, lifetimes: { attached() { const rect wx.getMenuButtonBoundingClientRect() const platform (wx.getDeviceInfo() || wx.getSystemInfoSync()).platform const isAndroid platform android const isDevtools platform devtools const { windowWidth, safeArea: { top 0, bottom 0 } {} } wx.getWindowInfo() || wx.getSystemInfoSync() this.setData({ ios: !isAndroid, innerPaddingRight: padding-right: ${windowWidth - rect.left}px, leftWidth: width: ${windowWidth - rect.left}px, safeAreaTop: isDevtools || isAndroid ? height: calc(var(--height) ${top}px); padding-top: ${top}px : }) }, }, methods: { _showChange(show) { const animated this.data.animated let displayStyle if (animated) { displayStyle opacity: ${show ? 1 : 0};transition:opacity 0.5s; } else { displayStyle display: ${show ? : none} } this.setData({ displayStyle }) }, back() { const data this.data if (data.delta) { wx.navigateBack({ delta: data.delta }) } this.triggerEvent(back, { delta: data.delta }, {}) } }, })这里有几个值得注意的点wx.getMenuButtonBoundingClientRect()获取右上角胶囊按钮的位置信息用来计算导航栏的右侧留白。wx.getWindowInfo()和wx.getDeviceInfo()新版 API替代了废弃的wx.getSystemInfoSync()。safeAreaTop处理刘海屏的安全区域Android 和开发者工具需要特殊处理。组件模板 navigation-bar.wxmlxmlview classweui-navigation-bar {{extClass}} view classweui-navigation-bar__inner {{ios ? ios : android}} stylecolor: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}}; !-- 左侧按钮 -- view classweui-navigation-bar__left style{{leftWidth}}; block wx:if{{back || homeButton}} block wx:if{{back}} view classweui-navigation-bar__buttons weui-navigation-bar__buttons_goback view bindtapback classweui-navigation-bar__btn_goback_wrapper ... view classweui-navigation-bar__button weui-navigation-bar__btn_goback/view /view /view /block !-- 首页按钮 -- /block block wx:else slot nameleft/slot /block /view !-- 标题 -- view classweui-navigation-bar__center view wx:if{{loading}} classweui-navigation-bar__loading.../view block wx:if{{title}} text{{title}}/text /block block wx:else slot namecenter/slot /block /view !-- 右侧 -- view classweui-navigation-bar__right slot nameright/slot /view /view /view组件支持三个插槽left、center、right灵活度很高。组件样式 navigation-bar.wxss样式部分比较长核心是用 CSS 变量控制高度css.weui-navigation-bar { --height: 44px; --left: 16px; } .weui-navigation-bar .android { --height: 48px; } .weui-navigation-bar__inner { height: calc(var(--height) env(safe-area-inset-top)); padding-top: env(safe-area-inset-top); display: flex; align-items: center; }返回箭头的图标用了 SVG 的mask技术这样可以通过background-color动态改变颜色比直接使用图片更灵活。三、计时器页面页面配置 index.jsonjson{ usingComponents: { navigation-bar: /components/navigation-bar/navigation-bar } }页面逻辑 index.jsjavascriptlet timerId null Page({ data: { hidden: true, num: 60 }, onLoad() { setTimeout(() { this.show() }, 2000) }, show() { this.setData({ hidden: false }) }, // 开始计时 start() { if (timerId ! null) return; timerId setInterval(() { this.timer() }, 1000) }, timer() { let n this.data.num console.log(n) if (n 0) { n-- this.setData({ num: n }) } else { clearInterval(timerId) timerId null } }, // 停止 stop() { clearInterval(timerId) timerId null }, // 重置 restart() { clearInterval(timerId) timerId null this.setData({ num: 60 }) } })这里有几个设计要点timerId放在 Page 外面作为模块级变量避免污染data也防止被setData影响。start()里的防抖如果timerId不为空说明已经在计时直接返回防止重复启动。倒计时到 0 自动停止在timer()里判断n 0。重置功能先清除定时器再把num恢复到 60。页面结构 index.wxmlxml!--index.wxml-- navigation-bar title计时器 back{{false}} colorblack background#FFF/navigation-bar scroll-view classscrollarea scroll-y typelist view classbox hidden{{hidden}} view classtitle计时器函数的应用/view view classtime{{num}}/view view classbtn-layout button typeprimary sizemini bind:tapstart开始计时/button button typeprimary sizemini bind:tapstop停止计时/button button typeprimary sizemini bind:taprestart重新计时/button /view /view /scroll-viewback{{false}}因为这是首页不需要返回按钮。hidden{{hidden}}页面加载后延迟 2 秒才显示内容做了一点入场动画的效果。页面样式 index.wxsscsspage { background: #fff; padding: 20rpx; } .time { width: 90%; background-color: blue; color: yellow; font-size: 200rpx; text-align: center; border: 2rpx solid red; border-radius: 60rpx; margin: 30rpx auto; position: relative; } .time::before { content: 逝者如斯夫 不舍昼夜; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 60rpx; color: rgba(255,255,255,0.3); z-index: 1; } .btn-wrap { display: flex; justify-content: center; gap: 20rpx; } button { width: 220rpx; }样式上有一个小巧思用.time::before伪元素在数字背后加了一层半透明的水印文字逝者如斯夫 不舍昼夜跟计时器的主题很搭。四、踩过的坑1.hidden属性失效一开始想用hidden控制.btn-layout的显隐后来发现hidden在自定义组件或某些情况下不生效建议用wx:if或者display: none。2. 定时器没有清除如果用户在倒计时结束后又点开始计时因为timerId被置为null了所以可以重新开始逻辑是正确的。但如果timer()里没有及时clearInterval可能出现多个定时器叠加的问题。3.restart没有立即更新视图setData({num: 60})是异步的如果在后面立即读取this.data.num可能拿到旧值。不过这里没有这个问题因为restart只是设置值不读取。五、可以改进的地方添加暂停/继续功能目前停止后是重新开始而不是从暂停的位置继续。可以把当前num保存下来。支持自定义时间通过input让用户设置倒计时秒数。音效提示倒计时结束时播放提示音。按钮状态管理计时中禁用开始按钮停止时禁用停止按钮。总结这个小项目虽然简单但涵盖了微信小程序开发的几个核心知识点自定义组件开发properties、slots、lifetimes定时器的正确使用与清理响应式数据绑定setData自定义导航栏与安全区域适配WXSS 伪元素和 CSS 变量的使用