本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
通常页面上获取验证码的按钮,每次点击过后,都会在几十秒内禁用,这是一个比较常见的需求。最近就碰到了这种需求,不同的点在于,需要处理的是一大推按钮。
项目需求是在一个表格中,每行数据都有几个按钮用于发起请求更改数据,那么肯定不能让用户连续点击同一个按钮,连续发起请求,不然服务器会受不了,引发各种令人头痛的问题。
但问题是页面是接手别人的,而且表格中按钮多,功能多,代码复杂,不适合大动干戈进行处理。
效果
思路分析
如果原有的代码不适合改动,那么能做就是在原有的代码上加一层处理逻辑。众所周知,浏览器的事件如果没有特意去处理,是会向上冒泡的,在所有按钮的共同父元素上挂载一个监听点击的事件,就可以监听到所有子元素的点击事件,也就是事件委托。
然后,可以从点击事件中获取到target属性,该属性指向被点击的元素。既然获取到了被点击的元素,那如果在那些不可连续点击的按钮上设置一些标识,是不是就可以区分出来,然后对这些按钮进行特殊处理即可。
代码
首先,在不可连续点击的按钮上,通过data-*属性,设置标识interval和禁用时间time:
<button
id="interval-btn"
data-interval="true"
data-time="50"
> 间隔按钮1 </button>
接着,在所有按钮的共同父级元素上挂载一个监听事件,对被点击的元素进行区分,挑选出不可连续点击的按钮,进行特殊处理,此处示例挂载在document.body:
// 当前禁用的按钮列表
let intervalList = []
// requestAnimationFrame 返回句柄
let intervalBtnTimer = 0
// 监听点击事件
document.body.addEventListener('click', (event) => {
// 获取被点击元素
const target = event.target
// 判断被点击元素是否需要进行禁用处理
if (target.dataset.interval !== 'true') {
return
}
const time = target.dataset.time
// 创建当前禁用的按钮列表的元素
const intervalItem = {
target,
time: (time ? +time : 50) * 1000
}
intervalList.push(intervalItem)
// 更改按钮样式,将其处理成不可点击状态
target.dataset.tip = Math.ceil(intervalItem.time / 1000) + '秒后可点击'
target.disabled = true
target.classList.add('showInterval')
// intervalBtnTimer是requestAnimationFrame返回的句柄,不为零,如果为零,代表没有当前没有执行计时;如果没有执行requestAnimationFrame,开始执行
intervalBtnTimer === 0 && (intervalBtnTimer = requestAnimationFrame(execIntervalBtnCheck))
})
上面的代码中使用requestAnimationFrame来进行计时操作,不选择使用setInterval,因为setInterval性能开销较大,而且也不好设定时间间隔,不如requestAnimationFrame方便快捷。
可以看到上面的requestAnimationFrame回调了execIntervalBtnCheck函数,该函数用于处理计时之后的操作,判断当前剩下的按钮禁用时间还剩多少,是否禁用时间到期,代码如下:
// 上次处理的时间戳,为零代表没有任何需要处理的按钮
let lastCheckIntervalTime = 0
/**
* @param { Number } currentTime requestAnimationFrame传给回调函数的时间戳
*/
function execIntervalBtnCheck(currentTime) {
// 如果上次处理的时间戳时间为零,代表没有任何需要处理的按钮,而现在处于执行,说明被上面`document.body`的`click`调用,所以先将当前时间记录下来
lastCheckIntervalTime === 0 && (lastCheckIntervalTime = currentTime)
// 计算上次执行的时间与本次执行的时间的时间差
const diffTime = currentTime - lastCheckIntervalTime
// 对于禁用的按钮列表进行遍历处理
const newIntervalList = intervalList.map(item => {
const { target } = item
// 计算当前按钮的剩余时间
item.time -= diffTime
// 剩余时间小于等于零时,解除按钮的禁用
if (item.time <= 0) {
target.classList.remove('showInterval')
target.dataset.tip = ''
target.disabled = false
return false
}
// 剩余时间大于等于零时,更新剩余时间提示
target.dataset.tip = Math.ceil(item.time / 1000) + '秒后可点击'
return item
}).filter(item => item) // filter过滤掉不再禁用的按钮
// 更新禁用按钮列表
intervalList = newIntervalList
// 记录当前时间戳,下次调用时,用于计算时间差
lastCheckIntervalTime = currentTime
if (intervalList.length > 0) {
// 如果还有禁用的按钮,开启下一次计时处理
intervalBtnTimer = requestAnimationFrame(execIntervalBtnCheck)
} else {
// 如果没有禁用的按钮,关闭计时处理,重置所有标志
lastCheckIntervalTime = 0
intervalBtnTimer = 0
}
}
至此,就实现了需求的功能。而且,对于源代码的改动也不大,只需要在每个需要限制的按钮上添加data-interval和data-time即可,处理的逻辑也钮本身的逻辑无关,保证不会影响员有的功能。