防止按钮重复点击的几种方案

October 3, 2020

问题描述

当前端使用 ajax 提交一个表单时,因网络缓慢等原因导致数据没有及时返回数据,导致用户没有得到正确的提示,以为未点中提交按钮,进而在此点击提交按钮,进行提交数据(多半会重复多次点击)

解决方法

  • 禁用按钮,并添加 loading 提示
  • 移除点击事件
  • 结合vue使用

常规点击请求代码

这里自己封装一个函数来模仿 ajax 请求

// 模拟ajax请求数据
function ajax() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 随机返回错误
      let res = parseInt(Math.random() * 10) % 2
      res === 0 ? resolve(1) : reject(2)
    }, 2000)
  })
}

理想状态下,点击按钮应返回数据,但因为网络缓慢,所以模拟数据回来要 2s


<button id="btn">点击</button>
<script>
  // 按钮点击次数
  var num = 0

  // 模拟ajax请求数据
  function ajax() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = parseInt(Math.random() * 10) % 2
        res === 0 ? resolve(1) : reject(2)
      }, 3000)
    })
  }

  const btn = document.getElementById('btn')
  btn.addEventListener('click', function() {
    ajax().then(res=> {
      console.log('数据回来了');
    }).catch(e=>{
      console.log('数据请求有问题呀');
    })
    console.log(++num);
  })

</script>

结果,作为新用户的我看到数据没有显示出来,以为没点中按钮,于是又多点了几下按钮。

image-20201005110057217

解决方案

禁用按钮,添加提示 loading

代码思路:将当前点击按钮禁用,并将按钮展示文本信息进行修改

<button id="btn">点击</button>
<p>0次</p>
<script>
  
  // 按钮点击次数
  var num = 0

  // 模拟ajax请求数据
  function ajax() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = parseInt(Math.random() * 10) % 2
        res === 0 ? resolve(1) : reject(2)
      }, 3000)
    })
  }

  const btn = document.getElementById('btn')
  
	var handleClick = function () {
    // 禁用按钮
    btn.setAttribute('disabled', true)
    // 文字提示
    btn.innerText = 'loading...'

    // 点击次数,渲染到p标签上
    num++
    document.querySelector('p').innerText = `${num}`

    // 模拟请求数据,并作出响应
    ajax()
      .then((res) => {
      console.log('点击成功: ' + res)
    })
      .catch((e) => {
      console.log('点击失败: ' + e)
    })
      .finally(() => {
      // 数据请求完毕,恢复按钮
      btn.removeAttribute('disabled')
      btn.innerText = '点击'
    })
  }
  btn.addEventListener('click', handleClick)
</script>

移除按钮点击事件

换个思路,不妨可以直接将点击事件移除,这样用户也同样不会触发多次点击事件

<button id="btn">点击</button>
<p>0次</p>
<script>
  // 按钮点击次数
  var num = 0

  // 模拟ajax请求数据
  function ajax() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = parseInt(Math.random() * 10) % 2
        res === 0 ? resolve(1) : reject(2)
      }, 3000)
    })
  }

  const btn = document.getElementById('btn')
  
	var handleClick = function () {
    // 移除按钮点击事件
    btn.removeEventListener('click', handleClick)
    btn.innerText = '加载中...'

    // 点击次数
    num++
    document.querySelector('p').innerText = `${num}`

    // 模拟请求数据,并作出响应
    ajax()
      .then((res) => {
      console.log('点击成功: ' + res)
    })
      .catch((e) => {
      console.log('点击失败: ' + e)
    })
      .finally(() => {
      // 数据请求完毕,恢复按钮
      btn.addEventListener('click', handleClick)
      btn.innerText = '点击'
    })
  }
  btn.addEventListener('click', handleClick)
</script>

以上两种方法结合使用

  • 禁用按钮
  • 添加提示
  • 移除点击事件
<button id="btn">点击</button>
<p></p>
<script>
  // 按钮点击次数
  var num = 0

  // 模拟ajax请求数据
  function ajax() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = parseInt(Math.random() * 10) % 2
        res === 0 ? resolve(1) : reject(2)
      }, 3000)
    })
  }

  const btn = document.getElementById('btn')
  var handleClick = function () {
    // 使按钮无效处理
    btn.removeEventListener('click', handleClick)
    btn.setAttribute('disabled', true)
    btn.innerText = '加载中...'

    // 点击次数
    num++
    document.querySelector('p').innerText = `${num}`

    // 模拟请求数据,并作出响应
    ajax()
      .then((res) => {
      console.log('点击成功: ' + res)
    })
      .catch((e) => {
      console.log('点击失败: ' + e)
    })
      .finally(() => {
      // 数据请求完毕,恢复按钮
      btn.addEventListener('click', handleClick)
      btn.removeAttribute('disabled')
      btn.innerText = '点击'
    })
  }
  btn.addEventListener('click', handleClick)
</script>

结合vue来使用

这里只是做了一个小的demo来解释下思路,多数情况下会用axios,可选择在封装axios请求时,进行封装这个操作。网上还有一些案例使用了指令来封装,思路有很多。

这里没有使用移除点击事件,可自行选择添加。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>防止用户同一时间内点击多次按钮,发送多次无效数据</title>
  </head>
  <body>
    <div id="root">
      <button @click="handleClick" ref="btn">点击</button>
      <p></p>
    </div>

    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
    <script>
      new Vue({
        el: '#root',
        methods: {
          // 对ajax请求封装
          ajax(button) {
            let text = ''
            if (button && button instanceof HTMLElement) {
              text = button.innerText || ''
              button.setAttribute('disabled', true)
              button.innerText = 'loading...'
            }
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                let res = parseInt(Math.random() * 10) % 2
                res === 0 ? resolve(1) : reject(2)
              }, 1000)
            })
              .then((res) => {
              	// 无论是 resolve 
                if (button) {
                  button.removeAttribute('disabled')
                  button.innerText = text
                }
                return res
              })
              .catch((e) => {
              	// 还是 catch,都能重置按钮
                if (button) {
                  button.removeAttribute('disabled')
                  button.innerText = text
                }
                throw e
              })
          },
          handleClick() {
            // 只需要在调用的时候传入按钮
            this.ajax(this.$refs.btn)
              .then((res) => {
                console.log('点击成功: ' + res)
              })
              .catch((e) => {
                console.log('点击失败: ' + e)
              })
          },
        },
      })
    </script>
  </body>
</html>