本章講解elementUI中用到的指令,自定義指令的基本方法可以參考官網(wǎng)-自定義指令。
說明:本文基于element-ui@2.13.0,源碼詳見element。
在src/directives下有兩個vue指令:mousewheel和repeat-click
mousewheel
在element-ui/packages/table/src/table.vue用到v-mousewheel指令。
import normalizeWheel from 'normalize-wheel';
const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const mousewheel = function(element, callback) {
if (element && element.addEventListener) {
element.addEventListener(isFirefox ? 'DOMMouseScroll' : 'mousewheel', function(event) {
const normalized = normalizeWheel(event);
callback && callback.apply(this, [event, normalized]);
});
}
};
export default {
bind(el, binding) {
mousewheel(el, binding.value);
}
};
代碼的重點是用到normalizeWheel:
normalize-wheel以dependencies的形式存在,主要下面三個問題:
- 解決不同瀏覽器、不同平臺的兼容性問題:
// Browsers
var _ie, _firefox, _opera, _webkit, _chrome;
// Actual IE browser for compatibility mode
var _ie_real_version;
// Platforms
var _osx, _windows, _linux, _android;
// Architectures
var _win64;
// Devices
var _iphone, _ipad, _native;
var _mobile;
- 內(nèi)部通過normalizeWheel.getEventType可以獲取到當前瀏覽器支持的滾動事件:
normalizeWheel.getEventType = function() /*string*/ {
return (UserAgent_DEPRECATED.firefox())
? 'DOMMouseScroll'
: (isEventSupported('wheel'))
? 'wheel'
: 'mousewheel';
};
- 內(nèi)部通過
isEventSupported,檢測滾輪監(jiān)控事件
/**
* Checks if an event is supported in the current execution environment.
* Borrows from Modernizr.
* @param {string} eventNameSuffix Event name, e.g. "click".
* @param {?boolean} capture Check if the capture phase is supported.
* @return {boolean} True if the event is supported.
* @internal
* @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
function isEventSupported(eventNameSuffix, capture) {
if (!ExecutionEnvironment.canUseDOM ||
capture && !('addEventListener' in document)) {
return false;
}
var eventName = 'on' + eventNameSuffix;
var isSupported = eventName in document;
if (!isSupported) {
var element = document.createElement('div');
element.setAttribute(eventName, 'return;');
isSupported = typeof element[eventName] === 'function';
}
if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
// This is the only way to test support for the `wheel` event in IE9+.
isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
}
return isSupported;
}
- 不同的瀏覽器,事件的滾動信息可能在detail、wheelDelta、wheelDeltaY或wheelDeltaX中,還有side scrolling的問題,以及滾動值單位問題,該工具庫通過
normalizeWheel進行了統(tǒng)一處理并對外暴露四個值spinX、spinY、pixelX、pixelY。
// Reasonable defaults
var PIXEL_STEP = 10;
var LINE_HEIGHT = 40;
var PAGE_HEIGHT = 800;
function normalizeWheel(/*object*/ event) /*object*/ {
var sX = 0, sY = 0, // spinX, spinY
pX = 0, pY = 0; // pixelX, pixelY
// Legacy
if ('detail' in event) { sY = event.detail; }
if ('wheelDelta' in event) { sY = -event.wheelDelta / 120; }
if ('wheelDeltaY' in event) { sY = -event.wheelDeltaY / 120; }
if ('wheelDeltaX' in event) { sX = -event.wheelDeltaX / 120; }
// side scrolling on FF with DOMMouseScroll
if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
sX = sY;
sY = 0;
}
pX = sX * PIXEL_STEP;
pY = sY * PIXEL_STEP;
if ('deltaY' in event) { pY = event.deltaY; }
if ('deltaX' in event) { pX = event.deltaX; }
if ((pX || pY) && event.deltaMode) {
if (event.deltaMode == 1) { // delta in LINE units
pX *= LINE_HEIGHT;
pY *= LINE_HEIGHT;
} else { // delta in PAGE units
pX *= PAGE_HEIGHT;
pY *= PAGE_HEIGHT;
}
}
// Fall-back if spin cannot be determined
if (pX && !sX) { sX = (pX < 1) ? -1 : 1; }
if (pY && !sY) { sY = (pY < 1) ? -1 : 1; }
return { spinX : sX,
spinY : sY,
pixelX : pX,
pixelY : pY };
}
repeat-click

在el-input-number組件中,點+、-時,會用到v-repeat-click

v-repeat-click會注冊mousedown事件,當用戶連續(xù)點擊+時:
- 當用戶鼠標左鍵一直按住不松手,只會觸發(fā)一次觸發(fā)
mousedown的回調(diào),但實際測量el-input-number發(fā)現(xiàn),輸入框中的數(shù)字會持續(xù)變大,原因就在于mousedown回調(diào)中加入了定時器,當鼠標松開,觸發(fā)一次mouseup回調(diào)方法,取消該定時器;這也許是directive為什么叫repeat-click的緣故吧; - 如果時間間隔大于100毫秒,那么
mousedown的回調(diào)方法里的setInterval回調(diào)就會執(zhí)行(及handler,本質(zhì)上就是執(zhí)行上圖的decrease或increase方法);
如果時間間隔小于100毫秒,定時器就會取消; -
mousedown的回調(diào)方法(clear方法)每次執(zhí)行時,都會通過once方法注冊并執(zhí)行一次mouseup回調(diào); - 在
mouseup回調(diào)中,如果發(fā)現(xiàn)距離最近一次點擊時間小于100ms,就會執(zhí)行一次handler方法,并清除定時器;
import { once, on } from 'element-ui/src/utils/dom';
export default {
bind(el, binding, vnode) {
let interval = null;
let startTime;
const handler = () => vnode.context[binding.expression].apply(); // 調(diào)用傳入的方法
const clear = () => {
if (Date.now() - startTime < 100) {
handler();
}
clearInterval(interval);
interval = null;
};
on(el, 'mousedown', (e) => {
if (e.button !== 0) return;
startTime = Date.now();
once(document, 'mouseup', clear);
clearInterval(interval);
interval = setInterval(handler, 100);
});
}
};
repeat-click依賴element-ui/src/utils/dom中的兩個方法:once和on.
- on
很簡單,判斷是用addEventListener還是attachEvent來注冊事件監(jiān)控器:
export const on = (function() {
if (!isServer && document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
- once
從語義上來看,就是注冊事件監(jiān)聽器并且只執(zhí)行一次,然后取消監(jiān)聽方法:
export const once = function(el, event, fn) {
var listener = function() {
if (fn) {
fn.apply(this, arguments);
}
off(el, event, listener); // 跟`on`方法相反,用來取消事件監(jiān)聽器
};
on(el, event, listener);
};
推薦
ElementUI的結(jié)構(gòu)與源碼研究
elementUI——mixins
elementUI——locale,國際化方案
elementU——transitions
elementUI——主題