elementUI——directives:mousewheel & repeat-click

本章讲解elementUI中用到的指令,自定义指令的基本方法可以参考官网-自定义指令。
说明:本文基于[email protected],源码详见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;
  • 内部通过normalizeWheel.getEventType可以获取到当前浏览器支持的滚动事件:
normalizeWheel.getEventType = function() /*string*/ {
  return (UserAgent_DEPRECATED.firefox())
           ? 'DOMMouseScroll'
           : (isEventSupported('wheel'))
               ? 'wheel'
               : 'mousewheel';
};
  • 内部通过isEventSupported,检测滚轮监控事件
/**
 * 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进行了统一处理并对外暴露四个值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事件,当用户连续点击+时:

  1. 当用户鼠标左键一直按住不松手,只会触发一次触发mousedown的回调,但实际测量el-input-number发现,输入框中的数字会持续变大,原因就在于mousedown回调中加入了定时器,当鼠标松开,触发一次mouseup回调方法,取消该定时器;这也许是directive为什么叫repeat-click的缘故吧;
  2. 如果时间间隔大于100毫秒,那么mousedown的回调方法里的setInterval回调就会执行(及handler,本质上就是执行上图的decreaseincrease方法);
    如果时间间隔小于100毫秒,定时器就会取消;
  3. mousedown的回调方法(clear方法)每次执行时,都会通过once方法注册并执行一次mouseup回调;
  4. mouseup回调中,如果发现距离最近一次点击时间小于100ms,就会执行一次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(); // 调用传入的方法
    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中的两个方法:onceon.

  • on
    很简单,判断是用addEventListener还是attachEvent来注册事件监控器:
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
    从语义上来看,就是注册事件监听器并且只执行一次,然后取消监听方法:
export const once = function(el, event, fn) {
  var listener = function() {
    if (fn) {
      fn.apply(this, arguments);
    }
    off(el, event, listener); // 跟`on`方法相反,用来取消事件监听器
  };
  on(el, event, listener);
};

推荐

ElementUI的结构与源码研究
elementUI——mixins
elementUI——locale,国际化方案
elementU——transitions
elementUI——主题

你可能感兴趣的:(elementUI——directives:mousewheel & repeat-click)