# 点击约束

// 注入全局方法
(function() {
  if (window.hadResetAjaxForWaiting) { // 如果已经重置过,则不再进入。解决开发时局部刷新导致重新加载问题
    return
  }
  window.hadResetAjaxForWaiting = true
  window.waittingAjaxMap = {} // 接口映射 'get::http://yapi.luckly-mjw.cn/mock/50/test/users?pageIndex=1': dom

  let OriginXHR = window.XMLHttpRequest
  let originOpen = OriginXHR.prototype.open
  let isSameSpace = false // 是否在同一个宏任务中,避免频繁触发

  // 检测使用到的 dom 对象
  function checkDom() {
    if (!isSameSpace) { // 节流
      isSameSpace = true
      window.waittingAjaxMap = {} // 重置为空,重新寻找匹配的 dom
      const domList = document.querySelectorAll('[data-loading]')
      Array.prototype.forEach.call(domList, targetDom => {
        targetDom.dataset.loading.split(',').forEach(targetUrl => {
          targetUrl = targetUrl.replace(/['"[\]]/ig, '').trim()
          window.waittingAjaxMap[targetUrl] = [targetDom, ...(window.waittingAjaxMap[targetUrl] || [])]
        })
      })
      setTimeout(() => isSameSpace = false) // 下一个宏任务中,重新开放该方法
    }
  }

  // 重置 XMLHttpRequest
  window.XMLHttpRequest = function() {
    let targetDomList = [] // 存储本 ajax 请求,影响到的 dom 元素
    let realXHR = new OriginXHR() // 重置操作函数,获取请求数据

    realXHR.open = function(method, url) {
      checkDom()
      Object.keys(window.waittingAjaxMap).forEach(key => {
        let [targetMethod, type, targetUrl] = key.split('::')
        if (!targetUrl) { // 设置默认类型
          targetUrl = type
          type = 'v-waiting-waiting'
        } else { // 指定类型
          type = `v-waiting-${type}`
        }
        if (targetMethod.toLocaleLowerCase() === method.toLocaleLowerCase() && (url.indexOf(targetUrl) > -1 || new RegExp(targetUrl).test(url))) {

          targetDomList = [...window.waittingAjaxMap[key], ...targetDomList]
          window.waittingAjaxMap[key].forEach(dom => {
            if (!dom.classList.contains(type)) {
              dom.classList.add('v-waiting', type)
              if (window.getComputedStyle(dom).position === 'static') { // 如果是 static 定位,则修改为 relative,为伪类的绝对定位做准备
                dom.style.position = 'relative'
              }
            }
            dom.waitingAjaxNum = dom.waitingAjaxNum || 0 // 不使用 dataset,是应为 dataset 并不实时,在同一个时间内,上一次存储的值不能被保存
            dom.waitingAjaxNum++
          })
        }
      })
      originOpen.call(realXHR, method, url)
    }

    // 监听加载完成,清除 waiting
    realXHR.addEventListener('loadend', () => {
      targetDomList.forEach(dom => {
        dom.waitingAjaxNum--
        dom.waitingAjaxNum === 0 && dom.classList.remove(
          'v-waiting',
          'v-waiting-loading',
          'v-waiting-waiting',
          'v-waiting-disable',
        )
      })
    }, false)
    return realXHR
  }
})();

// 注入全局 css
(() => {
  if (!document.getElementById('v-waiting')) {
    let code = `
       .v-waiting {
    pointer-events: none;
    /*cursor: not-allowed; 与 pointer-events: none 互斥,设置 pointer-events: none 后,设置鼠标样式无效 */
  }
  .v-waiting::before {
    position: absolute;
    content: '';
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    opacity: 0.7 !important;
    z-index: 9999 !important;
    background-color: #ffffff !important;
  }
  .v-waiting-waiting::after {
    position: absolute;
    content: '数据加载中';
    top: 50%;
    left: 0;
    width: 100%;
    max-width: 100vw;
    color: #666666;
    font-size: 20px;
    text-align: center;
    transform: translateY(-50%);
    z-index: 9999;
    animation: v-waiting-v-waiting-keyframes 1.8s infinite linear;
  }
   @-webkit-keyframes v-waiting-v-waiting-keyframes {
    20% {
      content: '数据加载中.';
    }
    40% {
      content: '数据加载中..';
    }
    60% {
      content: '数据加载中...';
    }
    80% {
      content: '数据加载中...';
    }
  }  
  .v-waiting-loading::after {
    position: absolute;
    content: '';
    left: 50% !important;
    top: 50% !important;
    width: 30px;
    height: 30px;
    z-index: 9999 !important;
    cursor: not-allowed;
    animation: v-waiting-v-loading-keyframes 1.1s infinite linear;
    background-position: center;
    background-size: 30px 30px;
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAMAAABgZ9sFAAAAS1BMVEUAAAAypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV0ypV1HXd+eAAAAGHRSTlMAsCqA4guKPxXUY0mbH/TszbzGpTZ0VW0vSgG0AAABLklEQVRIx9WUWbaDIBBEQSYVnE3S+1/p88XCOIGYv9SXwj1tU1TLTmU0d4KlKqN/yURaFfSWTsNfBOVJOPf4GIHyscKT8HgWL9nO+xa0VEG6QLfrbsog7b/P59dSTo922WzbnUkDQcpbv2qkJqq3JvHI6arjugZdBFzody4Cf7AzjfYQk7c1T5Yq9eI6Y78ndafpihMVj3uTRuUVZ4VdTRrZCIoQCiTiejJz5Nt4vIvRwMynevywqpmQZkp1P9N1xEyEqsg+c2cuL8coHLpvhl9M17eyruFlMi0iN1s6tys0UjiXT5o0bJYccHkarsNPoiPIBMJF4qx6E6xuN6cB7jCPWnZLOY0uT7oBjRfPS6kPbrX1ssh9u2mSM87v4UMCer8ZWN0qlsr3teQ5O+oPB7wgmvuSTDAAAAAASUVORK5CYII=);
  }
  @-webkit-keyframes v-waiting-v-loading-keyframes {
    from {
      transform: translate(-50%, -50%) rotate(0deg);
    }
    to {
      transform: translate(-50%, -50%) rotate(360deg);
    }
  }        `
    let style = document.createElement('style')
    style.id = 'v-waiting'
    style.type = 'text/css'
    style.rel = 'stylesheet'
    style.appendChild(document.createTextNode(code))
    let head = document.getElementsByTagName('head')[0]
    head.appendChild(style)
  }
})()

export default {}

把上面一段代码,引入页面即可

# 使用方式

<div data-loading='get::loading::/your/api'>点击</div>
<div data-loading='post::loading::/your/api'>点击post</div>
<table data-loading='get::loading::/your/api/list'>表格</table>
<div data-loading='post::loading::/your/api$'>点击post, 正则用法,后面加$</div>

以上点击约束代码由大佬发明,一劳永逸的点击约束解决方案 (opens new window)

更新时间: 2021年7月1日星期四下午2点21分