# 点击约束
// 注入全局方法
(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)
← 实用API Promise使用和实现方法 →