此篇分析一下 element-ui
组件库中,repeat-click
指令的作用和源代码。
一. repeat-click 指令的应用
repeat-click
指令用于在用户在长按鼠标左键时,持续的执行其绑定的方法,直到用户抬起鼠标左键。
在 element-ui
组件库中,有两处应用到了:
InputNumber
组件,在长按控制按钮(加号、减号)时,执行连续增加或减少。

TimePicker
时间选择器组件,在长按上下箭头时,执行时间的切换。

二. repeat-click 指令源码分析
2.1 repeat-click 源码实现思路
实现思路:
设定一个目标时间,用鼠标左键按下和抬起的持续时间与这个目标时间做对比:
- 第一种情况是小于目标时间,那么就执行一次绑定的方法。
- 第二种情况是大于目标时间,那么就以目标时间为间隔,持续的执行绑定的方法。
具体实现步骤:
- 在 vue 自定义指令的
bind
钩子中做处理,先设置间隔的时间,mac 为 100 毫秒,其余为 200 毫秒,然后将绑定的方法取出。 - 增加
mousedown
事件的监听,先去判断点击的是否为鼠标左键,如果不是,直接return
;如果是鼠标左键,则记录按下鼠标左键的时间,并且设置计时器,以目标时间为间隔持续的执行绑定的方法。 - 绑定鼠标的
mouseup
事件,判断如果鼠标抬起时间减去按下时间小于间隔时间,则执行一次绑定的方法。并且在mouseup
时,还需要清除计时器,停止执行绑定的方法。
2.2 repeat-click 源码分析
js
export default {
bind(el, binding, vnode) {
// 设置计时器
let interval = null;
let startTime;
// 每隔多长时间执行一次方法
const maxIntervals = isMac() ? 100 : 200;
// 绑定的方法
const handler = () => vnode.context[binding.expression].apply();
// 清除方法
const clear = () => {
// 小于间隔时间,则执行方法
if (Date.now() - startTime < maxIntervals) {
handler();
}
// 并且在鼠标抬起时清除计时器
clearInterval(interval);
interval = null;
}
on(el, 'mousedown', (e) => {
// 如果点击的不是鼠标左键,则直接 return
if (e.button !== 0) return;
// 变量 startTime 记录当前鼠标 mousedown 的时间
startTime = Date.now();
// 绑定鼠标 mouseup 事件,在 mouseup 事件时执行清除方法
once(document, 'mouseup', clear);
clearInterval(interval);
// 每隔 maxIntervals 时间执行一次方法
interval = setInterval(handler, maxIntervals);
})
}
}
涉及到的封装监听事件、以及移除监听事件的相关方法:
(1)on 方法:用于绑定监听事件,兼容 IE 浏览器
js
export const on = (function() {
if (!isServer && document.addEventListener) {
// 判断 document.addEventListener 是否存在,
return function(element, event, handler) {
// 如果元素、事件、回调方法都存在则绑定监听事件
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
// 不存在,使用 attachEvent 兼容IE浏览器
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
(2)off 方法:用于取消事件监听,兼容 IE 浏览器
js
export const off = (function() {
if (!isServer && document.removeEventListener) {
// 判断 document.removeEventListener 是否存在
return function(element, event, handler) {
// 如果元素、事件存在,则取消监听的绑定事件
if (element && event) {
element.removeEventListener(event, handler, false);
}
};
} else {
// 不存在,使用 detachEvent 兼容IE浏览器
return function(element, event, handler) {
if (element && event) {
element.detachEvent('on' + event, handler);
}
};
}
})();
(3)once 方法:用于监听绑定事件后,回调函数只触发一次
js
export const once = function(el, event, fn) {
var listener = function() {
// 如果传入了回调函数,则执行该函数
if (fn) {
fn.apply(this, arguments);
}
// 取消事件监听
off(el, event, listener);
};
// 绑定事件监听
on(el, event, listener);
};
三. InputNumber 组件使用该指令的具体情况
html
<template>
<!-- 使用自定义指令 -->
<span
v-repeat-click="decrease"
>
</span>
</template>
<script>
// 引入自定义指令
import RepeatClick from '../../utils/directives/repeat-click';
// 注册自定义指令
export default {
directives: {
repeatClick: RepeatClick
},
}
</script>