Skip to content

老旧浏览器API兼容

一些常用的工具方法

js
(function () {
  var _ = Object.create(null);
  var toTagString = Object.prototype.toString;

  /**
   * 判断变量是否是原始值
   * @param {any} value
   * @returns {Boolean}
   */
  _.isPrimitive = function (value) {
    return (
      typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean'
    );
  };

  /**
   * 获取变量类型
   * @param {any} val
   * @returns {string}
   */
  _.getRawType = function (val) {
    return toTagString.call(val).slice(8, -1);
  };

  /**
   * 判断变量是否是对象
   * @param {any} obj
   * @returns {Boolean}
   */
  _.isObject = function (obj) {
    return obj !== null && typeof obj === 'object';
  };

  /**
   * 判断对象是构造函数构造的
   * @param {any} obj
   * @returns {Boolean}
   */
  _.isInstanceOf = function (obj, constructor) {
    return toTagString.call(obj) === '[object ' + constructor + ']';
  };

  /**
   * 判断是否是 Promise
   * @param {any} val
   * @returns {Boolean}
   */
  _.isPromise = function (val) {
    return val && typeof val.then === 'function' && typeof val.catch === 'function';
  };

  /**
   * getElementById 的别名方法
   * @param {string} id selector
   * @returns {HTMLElement}
   */
  _.$ = function (id) {
    return document.getElementById(id);
  };

  /**
   * 判断是否是一个 HTMLElement 元素
   * @param {any} el
   * @returns {Boolean}
   */
  _.isElement = function (obj) {
    try {
      return obj instanceof HTMLElement;
    } catch (e) {
      return (
        typeof obj === 'object' &&
        obj.nodeType === 1 &&
        typeof obj.style === 'object' &&
        typeof obj.ownerDocument === 'object'
      );
    }
  };

  /**
   * 根据字符串查询元素
   * @param {string} selector
   * @param {HTMLElement|Document} el
   * @returns
   */
  _.$findOne = function (selector, el) {
    if (typeof selector !== 'string' || selector.length === 0) {
      throw new TypeError('$findOne: selector must be string');
    }
    if (typeof el !== 'undefined' && el && el.nodeType !== Node.ELEMENT_NODE) {
      throw new TypeError('$findOne: el must be a HTMLElement');
    }
    var el = el || document;
    var items;
    if (selector.substr(0, 1) === '.') {
      items = el.getElementsByClassName(selector.substr(1));
    } else {
      items = el.getElementsByTagName(selector);
    }
    return items ? items[0] : items;
  };

  /**
   * 根据 nodeType 来获取过滤对应的节点
   * @param {HTMLElementNodes} node
   * @returns {Array}
   */
  _.getChildrenByNodeType = function (node, nodeType) {
    var nodeType = nodeType || Node.ELEMENT_NODE;
    var nodes = [];
    var allowTypes = [
      Node.ELEMENT_NODE,
      Node.TEXT_NODE,
      Node.CDATA_SECTION_NODE,
      Node.PROCESSING_INSTRUCTION_NODE,
      Node.COMMENT_NODE,
      Node.DOCUMENT_NODE,
      Node.DOCUMENT_TYPE_NODE,
      Node.DOCUMENT_FRAGMENT_NODE,
    ];

    if (!_.isElement(node)) {
      throw new TypeError('getChildElementNodes: The node must be a HTMLElement');
    }

    if (allowTypes.indexOf(nodeType) === -1) {
      throw new TypeError('getChildElementNodes: unknown nodeType');
    }

    if (!node.hasChildNodes()) {
      return nodes;
    }

    var item;
    var children = node.childNodes;
    for (var i = 0; i < children.length; i++) {
      item = children[i];
      item.nodeType === nodeType && nodes.push(item);
    }
    return nodes;
  };

  /**
   * 根据当前元素找兄弟元素
   * @param {HTMLElement} node
   * @returns
   */
  _.findSiblings = function (node, index) {
    if (!_.isElement(node)) {
      throw new TypeError('findSiblings: node must be HTMLElement');
    }
    var index = index >> 0;
    var steps = Math.abs(index);
    var target = undefined;

    // 传入的数字为0
    if (steps === 0) {
      return node;
    }

    // 传入的 index 大于父元素的所有子元素数
    if (this.parentNode.childElementCount < steps) {
      return target;
    }

    // 找的方向: 大于0向后找, 小于0向前找
    var dirKey = index > 0 ? 'nextElementSibling' : 'previousElementSibling';
    var item = node;
    target = item;
    for (var i = 0; i < steps; i++) {
      if (item) {
        item = item[dirKey];
        target = item;
      } else {
        target = undefined;
      }
    }

    return target;
  };

  /**
   * 兼容: 获取offsetTop/offsetLeft, 惰性函数(只会判断一次)
   * @returns {Object: {top, left}}
   */
  _.getScrollOffset = (function (win) {
    var window = win,
      document = win.document;
    if (window.pageXOffset) {
      return function () {
        return {
          top: window.pageXOffset,
          left: window.pageYOffset,
        };
      };
    }

    return function () {
      return {
        top: document.body.scrollTop + document.documentElement.scrollTop,
        left: document.body.scrollLeft + document.documentElement.scrollLeft,
      };
    };
  })(window);

  /**
   * 兼容: pageX/pageY
   * @param {Event} e
   * @returns {Object:{x, y}}
   */
  _.getPagePosition = function (e) {
    var scrollPos = _.getScrollOffset(),
      clientLeft = document.documentElement.clientLeft || 0,
      clientTop = document.documentElement.clientTop || 0;
    return {
      x: e.clientX + scrollPos.left - clientLeft,
      y: e.clientY + scrollPos.top - clientTop,
    };
  };

  /**
   * 兼容: 获取可视区域大小(惰性函数, 另外一种实现方式)
   * @returns {Object:{width, height}}
   */
  function getViewportSize() {
    if (window.innerWidth) {
      getViewportSize = function () {
        return {
          width: window.innerWidth,
          height: window.innerHeight,
        };
      };
    } else {
      getViewportSize = function () {
        // ie
        var key = document.compatMode === 'BackCompat' ? 'body' : 'documentElement';
        return {
          width: document[key].clientWidth,
          height: document[key].clientHeight,
        };
      };
    }
    return getViewportSize();
  }
  _.getViewportSize = getViewportSize;

  /**
   * 兼容: 获取滚动的宽度/高度
   * @returns {object: {width, height}}
   */
  function getScrollSize() {
    if (document.body.scrollHeight) {
      getScrollSize = function () {
        return {
          width: document.body.scrollWidth,
          height: document.body.scrollHeight,
        };
      };
    } else {
      getScrollSize = function () {
        return {
          width: document.documentElement.scrollWidth,
          height: document.documentElement.scrollHeight,
        };
      };
    }
    return getScrollSize();
  }
  _.getScrollSize = getScrollSize;

  /**
   * 兼容: 获取 getComputedStyle 的值
   * @returns {string|number|undefined}
   */
  function getStyles(el, prop) {
    if (window.getComputedStyle) {
      getStyles = function (el, prop) {
        var styles = window.getComputedStyle(el, null);
        return styles[prop] ? styles[prop] : undefined;
      };
    } else {
      getStyles = function (el, prop) {
        // ie
        var styles = el.currentStyle;
        return styles[prop] ? styles[prop] : undefined;
      };
    }
    return getStyles(el, prop);
  }
  _.getStyles = getStyles;

  /**
   * 兼容: 添加事件处理函数
   * @param {HTMLElement} el 事件源
   * @param {String} type 事件类型
   * @param {Function} handler 事件处理函数
   * @returns
   */
  function addEvent(el, type, handler, capture) {
    if (el.addEventListener) {
      addEvent = function (el, type, handler, capture) {
        el.addEventListener(type, handler, capture);
      };
    } else if (el.attachEvent) {
      addEvent = function (el, type, handler) {
        el.attachEvent('on' + type, function () {
          handler.call(el);
        });
      };
    } else {
      addEvent = function (el, type, handler) {
        el['on' + type] = handler;
      };
    }
    return addEvent(el, type, handler, capture);
  }
  _.addEvent = addEvent;

  /**
   * 兼容: 移除事件处理函数
   * @param {HTMLElement} el 事件源
   * @param {String} type 事件类型
   * @returns
   */
  function removeEvent(el, type, handler) {
    if (el.removeEventListener) {
      removeEvent = function (el, type, handler) {
        el.removeEventListener(type, handler);
      };
    } else if (el.detachEvent) {
      removeEvent = function (el, type, handler) {
        el.detachEvent('on' + type, handler);
      };
    } else {
      removeEvent = function (el, type, _handler) {
        el['on' + type] = null;
      };
    }
    return removeEvent(el, type, handler); // 只有这一次执行会判断
  }
  _.removeEvent = removeEvent;

  /**
   * 兼容: 取消冒泡
   * @param {Event} e
   * @returns
   */
  function cancelBubble(e) {
    if (e.stopPropagation) {
      cancelBubble = function (e) {
        e = e || window.event;
        return e.stopPropagation();
      };
    } else {
      cancelBubble = function (e) {
        e = e || window.event; // window.event 兼容ie8及其以下版本的浏览器
        e.cancelBubble = true;
      };
    }

    return cancelBubble(e);
  }
  _.cancelBubble = cancelBubble;

  /**
   * 兼容: 取消浏览器默认事件
   * @param {Event} e
   * @returns
   */
  function preventDefault(e) {
    var e = e || widow.event;
    if (e.preventDefault) {
      // w3c standard
      preventDefault = function (e) {
        e.preventDefault(e);
      };
    } else {
      // ie: 9/8/7/6
      preventDefault = function (e) {
        e = e || widow.event;
        e.returnValue = false;
      };
    }
    return preventDefault(e);
  }
  _.preventDefault = preventDefault;

  /**
   * 让一个元素可以拖动
   * @param {object} options 配置选项
   * @param {HTMLElement} options.el: 需要被拖动的元素
   * @param {Boolean} options.xMoveable: 水平方向是否可以拖动 default: true
   * @param {Boolean} options.yMoveable: 垂直方向是否可以拖动 default: true
   * @param {Boolean} options.moveOutside: 是否限制移动边界 default: true
   */
  _.drag = function (options) {
    if (!options || Object.prototype.toString.call(options) !== '[object Object]') {
      throw new TypeError('the options must be a object');
    }

    var el = options.el,
      offsetX,
      offsetY,
      maxWidth,
      maxHeight,
      xMoveable,
      yMoveable,
      moveOutside;

    if (!_.isElement(el)) {
      throw new TypeError("The 'el' must be a HTMLElement");
    }
    xMoveable = options.xMoveable === false ? false : true;
    yMoveable = options.yMoveable === false ? false : true;
    moveOutside = options.moveOutside === false ? false : true;

    // mouse down
    var handleMouseDown = function (e) {
      var e = e || window.event;
      var target = e.target || e.srcElement;
      offsetX = e.offsetX;
      offsetY = e.offsetY;
      maxWidth = window.innerWidth - target.offsetWidth - 1;
      maxHeight = window.innerHeight - target.offsetHeight - 1;
      addEvent(document, 'mousemove', handleMouseMove);
      addEvent(document, 'mouseup', handleMouseUp);
    };

    // mouse move
    var handleMouseMove = function (e) {
      var e = e || window.event;
      var mousePos = _.getPagePosition(e);
      var left, top;
      el.style.position = 'fixed';
      if (xMoveable) {
        left = mousePos.x - offsetX;
        if (moveOutside) {
          left = left >= maxWidth ? maxWidth : left;
          left = left <= 0 ? 0 : left;
        }
        el.style.left = left + 'px';
      }
      if (yMoveable) {
        top = mousePos.y - offsetY;
        if (moveOutside) {
          top = top >= maxHeight ? maxHeight : top;
          top = top <= 0 ? 0 : top;
        }
        el.style.top = top + 'px';
      }
      preventDefault(e);
      cancelBubble(e);
    };

    // mouse up
    var handleMouseUp = function (e) {
      var e = e || window.event;
      removeEvent(document, 'mousemove', handleMouseMove);
      removeEvent(el, 'mouseup', handleMouseUp);
    };

    addEvent(el, 'mousedown', handleMouseDown);
  };

  // freeze 不允许修改这个 utils
  if (typeof Object.freeze === 'function') {
    Object.freeze(_);
  }

  // 暴露给外界使用
  window._ = _;
})();

Released under the MIT License.