美文网首页jselementUI
elementUI——directives:mousewheel

elementUI——directives:mousewheel

作者: videring | 来源:发表于2020-04-21 18:08 被阅读0次

    本章讲解elementUI中用到的指令,自定义指令的基本方法可以参考官网-自定义指令
    说明:本文基于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;
    
    • 内部通过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

        本文链接:https://www.haomeiwen.com/subject/zurpihtx.html