倒计时按钮需用 date.now() 计算真实剩余时间并设 disabled=true,防抖+接口成功后才启动,配合 aria-live 和 title 提升可访问性,避免 settimeout 累加导致漂移。

点击后按钮变倒计时且禁用
核心是用 JavaScript 控制 disabled 属性和按钮文本,避免用户重复点击触发多次请求。倒计时必须基于真实时间差(而非循环 setTimeout 累加),否则容易漂移或错乱。
常见错误是直接写 setInterval(() => { sec-- }, 1000),没考虑异步延迟、页面切后台暂停等问题,导致倒计时不准甚至负数。
- 用
Date.now()记录开始时间,每次渲染时计算剩余秒数,更可靠 - 倒计时结束前始终设
button.disabled = true,防止绕过 UI 的重复提交 - 按钮初始状态建议带
data-countdown="60"自定义属性,方便复用和读取
倒计时结束后自动恢复按钮
不能只靠定时器“走完 60 次”来判断结束——页面被切换到后台时,setTimeout 可能被节流或暂停,实际耗时远超 60 秒,但倒计时已归零。
正确做法是在每次 tick 里对比当前时间与目标截止时间(开始时间 + 倒计时总秒数):
const start = Date.now();
const totalSec = 60;
const end = start + totalSec * 1000;
<p>function tick() {
const remainMs = end - Date.now();
if (remainMs ${Math.ceil(remainMs / 1000)}s 后重试;
requestAnimationFrame(tick); // 比 setInterval 更精准
}</p>
防抖 + 请求成功才启动倒计时
用户快速连点、网络慢、接口返回 400/500,都可能导致倒计时启动了但验证码没发出去,造成体验断裂。
- 按钮点击后立即加防抖(如
if (btn.dataset.pending === 'true') return;) - 仅在收到 HTTP 200 且响应体明确表示发送成功(如
res.code === 0)时,才调用倒计时函数 - 失败时需清除可能残留的定时器,并还原按钮状态
- 服务端也应限制同一手机号 60 秒内只能发一次,前端倒计时只是辅助体验
兼容性与可访问性注意点
部分屏幕阅读器对动态更新的按钮文本感知较弱,单纯改 textContent 不够;同时 iOS Safari 对 requestAnimationFrame 在后台标签页的行为不一致。
- 给按钮加
aria-live="polite",让读屏软件播报倒计时变化 - 补充
title属性(如title="60 秒后可重新获取"),鼠标悬停可见 - 降级方案:如果
requestAnimationFrame不可用,回退到setTimeout,但每次重算剩余毫秒数 - 别用
pointer-events: none替代disabled—— 它不阻止键盘Enter触发,也不影响表单提交逻辑
倒计时逻辑看似简单,真正稳定运行要同时处理时间精度、请求状态、用户误操作、辅助技术支持这四条线,漏掉任意一条都可能在线上被放大成批量投诉。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











