Greasy Fork is available in English.

DOMUtils

使用js重新对jQuery的部分函数进行了仿写

Fra og med 13.09.2023. Se den nyeste version.

Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greasyfork.org/scripts/465772/1249783/DOMUtils.js

/**
 * 自己常用的元素工具类
 * @copyright GPL-3.0-only
 * @author WhiteSev
 **/
(function (global, factory) {
  /**
   * 不使用define
   * typeof define === "function" && define.amd
   * define(factory)
   */
  if (typeof exports === "object" && typeof module !== "undefined") {
    /* 适用于NodeJs或typeScript */
    module.exports = factory();
  } else {
    global = typeof globalThis !== "undefined" ? globalThis : global || self;
    /* 适用于浏览器中,且this对象是window,如果this是其它,那么会在其它对象下注册对象 */
    global.DOMUtils = factory(global.DOMUtils);
  }
})(typeof window !== "undefined" ? window : this, function (AnotherDOMUtils) {
  const DOMUtils = {};
  /**
   * @type {string} 元素工具类的版本
   */
  DOMUtils.version = "2023-9-13";

  let globalUtils = {
    /**
     * 用于显示元素并获取它的高度宽度等其它属性
     * @param {Element} element
     * @returns {{recovery: Function}} - 恢复
     */
    showElement: function (element) {
      let oldCSS_display = element.style.display;
      let oldCSS_visibility = element.style.visibility;
      let oldCSS_position = element.style.position;
      element.style.display = "block";
      element.style.visibility = "hidden";
      element.style.position = "absolute";
      return {
        recovery() {
          element.style.display = oldCSS_display;
          element.style.visibility = oldCSS_visibility;
          element.style.position = oldCSS_position;
        },
      };
    },
  };
  /**
   * 获取或设置元素的属性值
   * @param {HTMLElement|string} element 目标元素
   * @param {string} attrName 属性名
   * @param {string} [attrValue] 属性值(可选)
   * @returns {?string} 如果传入了attrValue,则返回undefined;否则返回属性值
   * @example
   * // 获取a.xx元素的href属性
   * DOMUtils.attr(document.querySelector("a.xx"),"href");
   * DOMUtils.attr("a.xx","href");
   * > https://xxxx....
   * @example
   * // 修改a.xx元素的href属性为abcd
   * DOMUtils.attr(document.querySelector("a.xx"),"href","abcd");
   * DOMUtils.attr("a.xx","href","abcd");
   * */
  DOMUtils.attr = function (element, attrName, attrValue) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (attrValue === undefined) {
      return element.getAttribute(attrName);
    } else {
      element.setAttribute(attrName, attrValue);
    }
  };

  /**
   * 创建元素
   * @param {string} tagName 元素类型
   * @param {object} property 元素属性
   * @returns {Element}
   * @example
   * // 创建一个DIV元素,且属性class为xxx
   * DOMUtils.createElement("div",{ class:"xxx" });
   * > <div class="xxx"></div>
   * @example
   * // 创建一个DIV元素
   * DOMUtils.createElement("div");
   * > <div></div>
   */
  DOMUtils.createElement = function (tagName, property) {
    let tempElement = document.createElement(tagName);
    if (typeof property === "string") {
      tempElement.innerHTML = property;
      return tempElement;
    }
    if (property == null) {
      return tempElement;
    }
    Object.keys(property).forEach((key) => {
      if (key in tempElement || typeof property[key] === "object") {
        tempElement[key] = property[key];
      } else {
        tempElement.setAttribute(key, property[key]);
      }
    });
    return tempElement;
  };

  /**
   * 获取或设置元素的样式属性值
   * @param {Element|string} element 目标元素
   * @param {string|object} property 样式属性名或包含多个属性名和属性值的对象
   * @param {string} [value] 样式属性值(可选)
   * @returns {?string} 如果传入了value,则返回undefined;否则返回样式属性值
   * @example
   * // 获取元素a.xx的CSS属性display
   * DOMUtils.css(document.querySelector("a.xx"),"display");
   * DOMUtils.css("a.xx","display");
   * > "none"
   * @example
   * // 设置元素a.xx的CSS属性display为block
   * DOMUtils.css(document.querySelector("a.xx"),"display","block");
   * DOMUtils.css(document.querySelector("a.xx"),"display","block !important");
   * DOMUtils.css(document.querySelector("a.xx"),{ display: "block" }});
   * DOMUtils.css(document.querySelector("a.xx"),{ display: "block !important" }});
   * DOMUtils.css("a.xx","display","block");
   * DOMUtils.css("a.xx","display","block !important");
   * @example
   * // 设置元素a.xx的CSS属性top为10px
   * DOMUtils.css(document.querySelector("a.xx"),"top","10px");
   * DOMUtils.css(document.querySelector("a.xx"),"top",10);
   * DOMUtils.css(document.querySelector("a.xx"),{ top: "10px" });
   * DOMUtils.css(document.querySelector("a.xx"),{ top: 10 });
   * */
  DOMUtils.css = function (element, property, value) {
    /**
     * 把纯数字没有px的加上
     */
    function handlePixe(propertyName, propertyValue) {
      let allowAddPixe = [
        "width",
        "height",
        "top",
        "left",
        "right",
        "bottom",
        "font-size",
      ];
      if (typeof propertyValue === "number") {
        propertyValue = propertyValue.toString();
      }
      if (
        typeof propertyValue === "string" &&
        allowAddPixe.includes(propertyName) &&
        propertyValue.match(/[0-9]$/gi)
      ) {
        propertyValue = propertyValue + "px";
      }
      return propertyValue;
    }
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof property === "string") {
      if (value === undefined) {
        return getComputedStyle(element).getPropertyValue(property);
      } else {
        if (value === "string" && value.includes("!important")) {
          element.style.setProperty(property, value, "important");
        } else {
          value = handlePixe(property, value);
          element.style.setProperty(property, value);
        }
      }
    } else if (typeof property === "object") {
      for (let prop in property) {
        if (
          typeof property[prop] === "string" &&
          property[prop].includes("!important")
        ) {
          element.style.setProperty(prop, property[prop], "important");
        } else {
          property[prop] = handlePixe(prop, property[prop]);
          element.style.setProperty(prop, property[prop]);
        }
      }
    }
  };
  /**
   * 获取或设置元素的文本内容
   * @param {Element|element} element 目标元素
   * @param {string} [text] 文本内容(可选)
   * @returns {?string} 如果传入了text,则返回undefined;否则返回文本内容
   * @example
   * // 设置元素a.xx的文本内容为abcd
   * DOMUtils.text(document.querySelector("a.xx"),"abcd")
   * DOMUtils.text("a.xx","abcd")
   * DOMUtils.html("a.xx",document.querySelector("b"))
   * */
  DOMUtils.text = function (element, text) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (text === undefined) {
      return element.textContent || element.innerText;
    } else {
      if (text instanceof Node || text instanceof Element) {
        text = text.textContent || text.innerText;
      }
      if ("textContent" in element) {
        element.textContent = text;
      } else if ("innerText" in element) {
        element.innerText = text;
      }
    }
  };
  /**
   * 获取或设置元素的HTML内容
   * @param {Element|string} element 目标元素
   * @param {Element|string} [html] HTML内容|元素(可选)
   * @returns {?string} 如果传入了html,则返回undefined;否则返回HTML内容
   * @example
   * // 设置元素a.xx的文本内容为<b>abcd</b>
   * DOMUtils.html(document.querySelector("a.xx"),"<b>abcd</b>")
   * DOMUtils.html("a.xx","<b>abcd</b>")
   * DOMUtils.html("a.xx",document.querySelector("b"))
   * */
  DOMUtils.html = function (element, html) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (html == null) {
      return element.innerHTML;
    } else {
      if (html instanceof Node || html instanceof Element) {
        html = html.innerHTML;
      }
      if ("innerHTML" in element) {
        element.innerHTML = html;
      }
    }
  };
  /**
   * 绑定或触发元素的click事件
   * @param {Element|string} element 目标元素
   * @param {function} [handler] 事件处理函数(可选)
   * @example
   * // 触发元素a.xx的click事件
   * DOMUtils.click(document.querySelector("a.xx"))
   * DOMUtils.click("a.xx")
   * DOMUtils.click("a.xx",function(){
   *  console.log("触发click事件成功")
   * })
   * */
  DOMUtils.click = function (element, handler) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (handler === undefined) {
      DOMUtils.trigger(element, "click");
    } else {
      DOMUtils.on(element, "click", null, handler);
    }
  };

  /**
   * 绑定或触发元素的blur事件
   * @param {Element|string} element 目标元素
   * @param {function} [handler] 事件处理函数(可选)
   * @example
   * // 触发元素a.xx的blur事件
   * DOMUtils.blur(document.querySelector("a.xx"))
   * DOMUtils.blur("a.xx")
   * DOMUtils.blur("a.xx",function(){
   *  console.log("触发blur事件成功")
   * })
   * */
  DOMUtils.blur = function (element, handler) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (handler === undefined) {
      DOMUtils.trigger(element, "blur");
    } else {
      DOMUtils.on(element, "blur", null, handler);
    }
  };
  /**
   * 绑定或触发元素的focus事件
   * @param {Element|string} element 目标元素
   * @param {?function} [handler] 事件处理函数(可选)
   * @example
   * // 触发元素a.xx的focus事件
   * DOMUtils.focus(document.querySelector("a.xx"))
   * DOMUtils.focus("a.xx")
   * DOMUtils.focus("a.xx",function(){
   *  console.log("触发focus事件成功")
   * })
   * */
  DOMUtils.focus = function (element, handler) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (handler === undefined) {
      DOMUtils.trigger(element, "focus");
    } else {
      DOMUtils.on(element, "focus", null, handler);
    }
  };
  /**
   * 获取或设置元素的value属性值
   * @param {Element|string} element 目标元素
   * @param {string} [value] value属性值(可选)
   * @returns {string|undefined} 如果传入了value,则返回undefined;否则返回value属性值
   * @example
   * // 获取元素input.xx的复选框值
   * DOMUtils.val(document.querySelector("input.xx"))
   * DOMUtils.val("input.xx")
   * > true
   * @example
   * // 修改元素input.xx的复选框值为true
   * DOMUtils.val(document.querySelector("input.xx"),true)
   * DOMUtils.val("input.xx",true)
   * */
  DOMUtils.val = function (element, value) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (value == null) {
      if (
        element.localName === "input" &&
        (element.type === "checkbox" || element.type === "radio")
      ) {
        return element.checked;
      } else {
        return element.value;
      }
    } else {
      if (
        element.localName === "input" &&
        (element.type === "checkbox" || element.type === "radio")
      ) {
        element.checked = !!value;
      } else {
        element.value = value;
      }
    }
  };
  /**
   * 获取或设置元素的属性值
   * @param {Element|string} element 目标元素
   * @param {string} propName 属性名
   * @param {string} [propValue] 属性值(可选)
   * @returns {string|undefined} 如果传入了propValue,则返回undefined;否则返回属性值
   * @example
   * // 获取元素a.xx的属性data-value
   * DOMUtils.val(document.querySelector("a.xx"),"data-value")
   * DOMUtils.val("a.xx","data-value")
   * > undefined
   * @example
   * // 设置元素a.xx的属性data-value为1
   * DOMUtils.val(document.querySelector("a.xx"),"data-value",1)
   * DOMUtils.val("a.xx","data-value",1)
   * */
  DOMUtils.prop = function (element, propName, propValue) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (propValue == null) {
      return element[propName];
    } else {
      element[propName] = propValue;
    }
  };

  /**
   * 移除元素的属性
   * @param {Element|string} element 目标元素
   * @param {string} attrName 属性名
   * @example
   * // 移除元素a.xx的属性data-value
   * DOMUtils.removeAttr(document.querySelector("a.xx"),"data-value")
   * DOMUtils.removeAttr("a.xx","data-value")
   * */
  DOMUtils.removeAttr = function (element, attrName) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    element.removeAttribute(attrName);
  };

  /**
   * 移除元素class名
   * @param {Element|string} element 目标元素
   * @param {string} className class名
   * @returns {DOMUtils} 原型链
   * @example
   * // 移除元素a.xx的className为xx
   * DOMUtils.removeClass(document.querySelector("a.xx"),"xx")
   * DOMUtils.removeClass("a.xx","xx")
   */
  DOMUtils.removeClass = function (element, className) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (className == null) {
      return;
    }
    element.classList.remove(className);
  };

  /**
   * 移除元素的属性
   * @param {Element|string} element 目标元素
   * @param {string} propName 属性名
   * @example
   * // 移除元素a.xx的href属性
   * DOMUtils.removeProp(document.querySelector("a.xx"),"href")
   * DOMUtils.removeProp("a.xx","href")
   * */
  DOMUtils.removeProp = function (element, propName) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    delete element[propName];
  };

  /**
   * 将一个元素替换为另一个元素
   * @param {Element|string} element 目标元素
   * @param {Element|string} newElement 新元素
   * @returns {DOMUtils} 原型链
   * @example
   * // 替换元素a.xx为b.xx
   * DOMUtils.replaceWith(document.querySelector("a.xx"),document.querySelector("b.xx"))
   * DOMUtils.replaceWith("a.xx",'<b class="xx"></b>')
   */
  DOMUtils.replaceWith = function (element, newElement) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof newElement === "string") {
      newElement = DOMUtils.parseHTML(newElement, false, false);
    }
    if (element instanceof NodeList || element instanceof Array) {
      element.forEach((item) => {
        DOMUtils.replaceWith(item, newElement);
      });
    } else {
      element.parentElement.replaceChild(newElement, element);
    }
  };

  /**
   * 给元素添加class
   * @param {Element|string} element 目标元素
   * @param {string} className class名
   * @example
   * // 元素a.xx的className添加_vue_
   * DOMUtils.addClass(document.querySelector("a.xx"),"_vue_")
   * DOMUtils.addClass("a.xx","_vue_")
   * */
  DOMUtils.addClass = function (element, className) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    element.classList.add(className);
  };
  /**
   * 函数在元素内部末尾添加子元素或HTML字符串
   * @param {Element|string} element 目标元素
   * @param {object|string} content 子元素或HTML字符串
   * @example
   * // 元素a.xx的内部末尾添加一个元素
   * DOMUtils.append(document.querySelector("a.xx"),document.querySelector("b.xx"))
   * DOMUtils.append("a.xx","'<b class="xx"></b>")
   * */
  DOMUtils.append = function (element, content) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof content === "string") {
      element.insertAdjacentHTML("beforeend", content);
    } else {
      element.appendChild(content);
    }
  };

  /**
   * 函数 在元素内部开头添加子元素或HTML字符串
   * @param {Element|string} element 目标元素
   * @param {object|string} content 子元素或HTML字符串
   * @example
   * // 元素a.xx内部开头添加一个元素
   * DOMUtils.prepend(document.querySelector("a.xx"),document.querySelector("b.xx"))
   * DOMUtils.prepend("a.xx","'<b class="xx"></b>")
   * */
  DOMUtils.prepend = function (element, content) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof content === "string") {
      element.insertAdjacentHTML("afterbegin", content);
    } else {
      element.insertBefore(content, element.firstChild);
    }
  };
  /**
   * 在元素后面添加兄弟元素或HTML字符串
   * @param {Element|string} element 目标元素
   * @param {object|string} content 兄弟元素或HTML字符串
   * @example
   * // 元素a.xx后面添加一个元素
   * DOMUtils.after(document.querySelector("a.xx"),document.querySelector("b.xx"))
   * DOMUtils.after("a.xx","'<b class="xx"></b>")
   * */
  DOMUtils.after = function (element, content) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof content === "string") {
      element.insertAdjacentHTML("afterend", content);
    } else {
      element.parentElement.insertBefore(content, element.nextSibling);
    }
  };

  /**
   * 在元素前面添加兄弟元素或HTML字符串
   * @param {Element|string} element 目标元素
   * @param {object|string} content 兄弟元素或HTML字符串
   * @example
   * // 元素a.xx前面添加一个元素
   * DOMUtils.before(document.querySelector("a.xx"),document.querySelector("b.xx"))
   * DOMUtils.before("a.xx","'<b class="xx"></b>")
   * */
  DOMUtils.before = function (element, content) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof content === "string") {
      element.insertAdjacentHTML("beforebegin", content);
    } else {
      element.parentElement.insertBefore(content, element);
    }
  };

  /**
   * 移除元素
   * @param {Element|string|NodeList} element 目标元素
   * @example
   * // 元素a.xx前面添加一个元素
   * DOMUtils.remove(document.querySelector("a.xx"))
   * DOMUtils.remove(document.querySelectorAll("a.xx"))
   * DOMUtils.remove("a.xx")
   * */
  DOMUtils.remove = function (element) {
    if (typeof element === "string") {
      element = document.querySelectorAll(element);
    }
    if (element == null) {
      return;
    }
    if (element instanceof NodeList || element instanceof Array) {
      element.forEach(function (item) {
        DOMUtils.remove(item);
      });
    } else {
      element.remove();
    }
  };
  /**
   * 移除元素的所有子元素
   * @param {Element|string} element 目标元素
   * @example
   * // 移除元素a.xx元素的所有子元素
   * DOMUtils.empty(document.querySelector("a.xx"))
   * DOMUtils.empty("a.xx")
   * */
  DOMUtils.empty = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    while (element.firstChild) {
      element.removeChild(element.firstChild);
    }
  };
  /**
   * 绑定事件
   * @param {Element|string} element 需要绑定的元素
   * @param {string|Array} eventType 需要监听的事件
   * @param {HTMLElement?} selector 子元素选择器
   * @param {Function} callback 事件触发的回调函数
   * @param {Boolean} capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发
   * @param {Boolean} once 表示事件是否只触发一次。默认为false
   * @param {Boolean} passive 表示事件监听器是否不会调用preventDefault()。默认为false
   * @example
   * // 监听元素a.xx的click事件
   * DOMUtils.on(document.querySelector("a.xx"),"click",(event)=>{
   *    console.log("事件触发",event)
   * })
   * DOMUtils.on("a.xx","click",(event)=>{
   *    console.log("事件触发",event)
   * })
   * @example
   * // 监听元素a.xx的click、tap、hover事件
   * DOMUtils.on(document.querySelector("a.xx"),"click tap hover",(event)=>{
   *    console.log("事件触发",event)
   * })
   * DOMUtils.on("a.xx",["click","tap","hover"],(event)=>{
   *    console.log("事件触发",event)
   * })
   * @example
   * // 监听全局document下的子元素a.xx的click事件
   * DOMUtils.on(document,"click tap hover","a.xx",(event)=>{
   *    console.log("事件触发",event)
   * })
   */
  DOMUtils.on = function (
    element,
    eventType,
    selector,
    callback,
    capture = false,
    once = false,
    passive = false
  ) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    let eventTypeList = [];
    if (Array.isArray(eventType)) {
      eventTypeList = eventType;
    } else if (typeof eventType === "string") {
      eventTypeList = eventType.split(" ");
    }
    if (typeof selector === "function") {
      /* 这是为没有selector的情况 */
      callback = selector;
      selector = null;
    }
    let originCallBack = callback;
    let ownCallBack = function (event) {
      if (selector) {
        let target = event.target;
        let totalParent =
          element == window ? document.documentElement : element;
        if (target.matches(selector)) {
          /* 当前目标可以被selector所匹配到 */
          originCallBack.call(target, event);
          return;
        } else if (
          target.closest(selector) &&
          totalParent.contains(target.closest(selector))
        ) {
          /* 在上层与主元素之间寻找可以被selector所匹配到的 */
          let closestElement = target.closest(selector);
          /* event的target值不能直接修改 */
          Object.defineProperty(event, "target", {
            get: function () {
              return closestElement;
            },
          });
          originCallBack.call(closestElement, event);
          return;
        }
      } else {
        originCallBack.call(event.target, event);
      }
    };
    eventTypeList.forEach((_eventType_) => {
      element.addEventListener(
        _eventType_,
        ownCallBack,
        capture,
        once,
        passive
      );
    });

    if (originCallBack && originCallBack.delegate) {
      element.setAttribute("data-delegate", selector);
    }

    let events = element.events || {};
    events[eventType] = events[eventType] || [];
    events[eventType].push({
      selector: selector,
      callback: ownCallBack,
    });
    element.events = events;
  };
  /**
   * 取消绑定事件
   * @param {Element|string} element 需要取消绑定的元素
   * @param {string|Array} eventType 需要取消监听的事件
   * @param {?string} selector 子元素选择器
   * @param {Function} callback 事件触发的回调函数
   * @param {Boolean} useCapture 表示事件是否在捕获阶段处理,它是一个可选参数,默认为false,表示在冒泡阶段处理事件。
   * 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true
   * @example
   * // 取消监听元素a.xx的click事件
   * DOMUtils.off(document.querySelector("a.xx"),"click")
   * DOMUtils.off("a.xx","click")
   * // 取消监听元素a.xx的click、tap、hover事件
   * DOMUtils.off(document.querySelector("a.xx"),"click tap hover")
   * DOMUtils.off("a.xx",["click","tap","hover"])
   * // 取消监听全局下的a.xx的点击事件
   * DOMUtils.off(document,"click","a.xx")
   */
  DOMUtils.off = function (
    element,
    eventType,
    selector,
    callback,
    useCapture = false
  ) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    let events = element.events || {};
    let eventTypeList = [];
    if (!eventType) {
      for (let type in events) {
        eventTypeList = [...eventTypeList, type];
      }
    } else if (Array.isArray(eventType)) {
      eventTypeList = eventType;
    } else if (typeof eventType === "string") {
      eventTypeList = eventType.split(" ");
    }
    eventTypeList.forEach((_eventType_) => {
      let handlers = events[eventType] || [];
      for (let i = 0; i < handlers.length; i++) {
        if (
          (!selector || handlers[i].selector === selector) &&
          (!callback || handlers[i].callback === callback)
        ) {
          element.removeEventListener(
            _eventType_,
            handlers[i].callback,
            useCapture
          );
          handlers.splice(i--, 1);
        }
      }
      if (handlers.length === 0) {
        delete events[eventType];
      }
    });
    element.events = events;
  };
  /**
   * 主动触发事件
   * @param {Element|string} element 需要触发的元素
   * @param {String|Array} eventType 需要触发的事件
   * @example
   * // 触发元素a.xx的click事件
   * DOMUtils.trigger(document.querySelector("a.xx"),"click")
   * DOMUtils.trigger("a.xx","click")
   * // 触发元素a.xx的click、tap、hover事件
   * DOMUtils.trigger(document.querySelector("a.xx"),"click tap hover")
   * DOMUtils.trigger("a.xx",["click","tap","hover"])
   */
  DOMUtils.trigger = function (element, eventType) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    let events = element.events || {};
    let eventTypeList = [];
    if (!eventType) {
      for (let type in events) {
        eventTypeList = [...eventTypeList, type];
      }
    } else if (Array.isArray(eventType)) {
      eventTypeList = eventType;
    } else if (typeof eventType === "string") {
      eventTypeList = eventType.split(" ");
    }
    eventTypeList.forEach((_eventType_) => {
      let event = new Event(_eventType_);
      element.dispatchEvent(event);
    });
  };
  /**
   * 设置或返回被选元素相对于文档的偏移坐标
   * @param {Element|string} element
   * @returns {Object}
   * @example
   * // 获取元素a.xx的对于文档的偏移坐标
   * DOMUtils.offset(document.querySelector("a.xx"))
   * DOMUtils.offset("a.xx")
   * > 0
   */
  DOMUtils.offset = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    let rect = element.getBoundingClientRect();
    let win = window;
    return {
      top: rect.top + win.scrollY,
      left: rect.left + win.scrollX,
    };
  };
  /**
   * 获取元素的宽度
   * @param {Element|string} element 要获取宽度的元素
   * @returns {Number} 元素的宽度,单位为像素
   * @example
   * // 获取元素a.xx的宽度
   * DOMUtils.width(document.querySelector("a.xx"))
   * DOMUtils.width("a.xx")
   * > 100
   * // 获取window的宽度
   * DOMUtils.width(window)
   * > 400
   * @example
   * // 设置元素a.xx的宽度为200
   * DOMUtils.width(document.querySelector("a.xx"),200)
   * DOMUtils.width("a.xx",200)
   */
  DOMUtils.width = function (element) {
    if (element == window) {
      return window.document.documentElement.clientWidth;
    }
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (element.nodeType === 9) {
      /* 文档节点 */
      return Math.max(
        element.body.scrollWidth,
        element.documentElement.scrollWidth,
        element.body.offsetWidth,
        element.documentElement.offsetWidth,
        element.documentElement.clientWidth
      );
    }
    let handleElement = globalUtils.showElement(element);
    let view = element.ownerDocument.defaultView;
    if (!view || !view.opener) {
      view = window;
    }
    let styles = view.getComputedStyle(element);
    let elementPaddingLeft = parseFloat(styles.paddingLeft);
    let elementPaddingRight = parseFloat(styles.paddingRight);
    if (isNaN(elementPaddingLeft)) {
      elementPaddingLeft = 0;
    }
    if (isNaN(elementPaddingRight)) {
      elementPaddingRight = 0;
    }
    let elementWidth =
      element.clientWidth - elementPaddingLeft - elementPaddingRight;
    handleElement.recovery();
    return elementWidth;
  };
  /**
   * 获取元素的高度
   * @param {Element|string} element 要获取高度的元素
   * @returns {Number} 元素的高度,单位为像素
   * @example
   * // 获取元素a.xx的高度
   * DOMUtils.height(document.querySelector("a.xx"))
   * DOMUtils.height("a.xx")
   * > 100
   * // 获取window的高度
   * DOMUtils.height(window)
   * > 700
   * @example
   * // 设置元素a.xx的高度为200
   * DOMUtils.height(document.querySelector("a.xx"),200)
   * DOMUtils.height("a.xx",200)
   */
  DOMUtils.height = function (element) {
    if (element == window) {
      return window.document.documentElement.clientHeight;
    }
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (element.nodeType === 9) {
      /* 文档节点 */
      return Math.max(
        element.body.scrollHeight,
        element.documentElement.scrollHeight,
        element.body.offsetHeight,
        element.documentElement.offsetHeight,
        element.documentElement.clientHeight
      );
    }
    let handleElement = globalUtils.showElement(element);
    let view = element.ownerDocument.defaultView;
    if (!view || !view.opener) {
      view = window;
    }
    let styles = view.getComputedStyle(element);
    let elementPaddingTop = parseFloat(styles.paddingTop);
    let elementPaddingBottom = parseFloat(styles.paddingBottom);
    if (isNaN(elementPaddingTop)) {
      elementPaddingTop = 0;
    }
    if (isNaN(elementPaddingBottom)) {
      elementPaddingBottom = 0;
    }
    let elementHeight =
      element.clientHeight - elementPaddingTop - elementPaddingBottom;
    handleElement.recovery();
    return elementHeight;
  };
  /**
   * 获取元素的外部宽度(包括边框和外边距)
   * @param {Element|string} element 要获取外部宽度的元素
   * @returns {Number} 元素的外部宽度,单位为像素
   * @example
   * // 获取元素a.xx的外部宽度
   * DOMUtils.outerWidth(document.querySelector("a.xx"))
   * DOMUtils.outerWidth("a.xx")
   * > 100
   * // 获取window的外部宽度
   * DOMUtils.outerWidth(window)
   * > 400
   */
  DOMUtils.outerWidth = function (element) {
    if (element == window) {
      return window.innerWidth;
    }
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    let handleElement = globalUtils.showElement(element);
    let style = getComputedStyle(element, null);
    let elementMarginLeft = parseFloat(style.marginLeft);
    let elementMarginRight = parseFloat(style.marginRight);
    if (isNaN(elementMarginLeft)) {
      elementMarginLeft = 0;
    }
    if (isNaN(elementMarginRight)) {
      elementMarginRight = 0;
    }
    handleElement.recovery();
    return element.offsetWidth + elementMarginLeft + elementMarginRight;
  };
  /**
   * 获取元素的外部高度(包括边框和外边距)
   * @param {Element|string} element 要获取外部高度的元素
   * @returns {Number} 元素的外部高度,单位为像素
   * @example
   * // 获取元素a.xx的外部高度
   * DOMUtils.outerHeight(document.querySelector("a.xx"))
   * DOMUtils.outerHeight("a.xx")
   * > 100
   * // 获取window的外部高度
   * DOMUtils.outerHeight(window)
   * > 700
   */
  DOMUtils.outerHeight = function (element) {
    if (element == window) {
      return window.innerHeight;
    }
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    let handleElement = globalUtils.showElement(element);
    let style = getComputedStyle(element, null);
    let elementMarginTop = parseFloat(style.marginTop);
    let elementMarginBottom = parseFloat(style.marginBottom);
    if (isNaN(elementMarginTop)) {
      elementMarginTop = 0;
    }
    if (isNaN(elementMarginBottom)) {
      elementMarginBottom = 0;
    }
    handleElement.recovery();
    return element.offsetHeight + elementMarginTop + elementMarginBottom;
  };

  /**
   * 等待文档加载完成后执行指定的函数
   * @param {Function} callback 需要执行的函数
   * @example
   * DOMUtils.ready(function(){
   *   console.log("文档加载完毕")
   * })
   */
  DOMUtils.ready = function (callback) {
    function completed() {
      document.removeEventListener("DOMContentLoaded", completed);
      window.removeEventListener("load", completed);
      callback();
    }
    if (
      document.readyState === "complete" ||
      (document.readyState !== "loading" && !document.documentElement.doScroll)
    ) {
      window.setTimeout(callback);
    } else {
      /* 监听DOMContentLoaded事件 */
      document.addEventListener("DOMContentLoaded", completed);
      /* 监听load事件 */
      window.addEventListener("load", completed);
    }
  };

  /**
   * 在一定时间内改变元素的样式属性,实现动画效果
   * @param {Element|string} element 需要进行动画的元素
   * @param {Object} styles 动画结束时元素的样式属性
   * @param {Number} [duration=1000] 动画持续时间,单位为毫秒
   * @param {Function} [callback=null] 动画结束后执行的函数
   * @example
   * // 监听元素a.xx的从显示变为隐藏
   * DOMUtils.animate(document.querySelector("a.xx"),{ top:100},1000,function(){
   *   console.log("已往上位移100px")
   * })
   */
  DOMUtils.animate = function (
    element,
    styles,
    duration = 1000,
    callback = null
  ) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (typeof duration !== "number" || duration <= 0) {
      throw new TypeError("duration must be a positive number");
    }
    if (typeof callback !== "function" && callback !== null) {
      throw new TypeError("callback must be a function or null");
    }
    if (typeof styles !== "object" || styles === null) {
      throw new TypeError("styles must be an object");
    }
    if (Object.keys(styles).length === 0) {
      throw new Error("styles must contain at least one property");
    }
    let start = performance.now();
    let from = {};
    let to = {};
    for (let prop in styles) {
      from[prop] = element.style[prop] || getComputedStyle(element)[prop];
      to[prop] = styles[prop];
    }
    let timer = setInterval(function () {
      let timePassed = performance.now() - start;
      let progress = timePassed / duration;
      if (progress > 1) {
        progress = 1;
      }
      for (let prop in styles) {
        element.style[prop] =
          from[prop] + (to[prop] - from[prop]) * progress + "px";
      }
      if (progress === 1) {
        clearInterval(timer);
        if (callback) {
          callback();
        }
      }
    }, 10);
  };

  /**
   * 将一个元素包裹在指定的HTML元素中
   * @param {Element|string} element 要包裹的元素
   * @param {string} wrapperHTML 要包裹的HTML元素的字符串表示形式
   * @example
   * // 将a.xx元素外面包裹一层div
   * DOMUtils.wrap(document.querySelector("a.xx"),"<div></div>")
   */
  DOMUtils.wrap = function (element, wrapperHTML) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    // 创建一个新的div元素,并将wrapperHTML作为其innerHTML
    let wrapper = document.createElement("div");
    wrapper.innerHTML = wrapperHTML;

    wrapper = wrapper.firstChild;
    // 将要包裹的元素插入目标元素前面
    element.parentElement.insertBefore(wrapper, element);

    // 将要包裹的元素移动到wrapper中
    wrapper.appendChild(element);
  };
  /**
   * 获取当前元素的前一个兄弟元素
   * @param {Element|string} element 当前元素
   * @returns {?Element} 前一个兄弟元素
   * @example
   * // 获取a.xx元素前一个兄弟元素
   * DOMUtils.prev(document.querySelector("a.xx"))
   * DOMUtils.prev("a.xx")
   * > <div ...>....</div>
   */
  DOMUtils.prev = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    return element.previousElementSibling;
  };

  /**
   * 获取当前元素的后一个兄弟元素
   * @param {Element|string} element 当前元素
   * @returns {?Element} 后一个兄弟元素
   * @example
   * // 获取a.xx元素前一个兄弟元素
   * DOMUtils.next(document.querySelector("a.xx"))
   * DOMUtils.next("a.xx")
   * > <div ...>....</div>
   */
  DOMUtils.next = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    return element.nextElementSibling;
  };

  /**
   * 释放原有的DOMUtils控制权
   * @example
   * let DOMUtils = window.DOMUtils.noConflict()
   */
  DOMUtils.noConflict = function () {
    if (window.DOMUtils) {
      delete window.DOMUtils;
    }
    if (AnotherDOMUtils) {
      window.DOMUtils = AnotherDOMUtils;
    }
    return DOMUtils;
  };

  /**
   * 获取当前元素的所有兄弟元素
   * @param {Element|string} element 当前元素
   * @returns {?Array} 所有兄弟元素
   * @example
   * // 获取a.xx元素所有兄弟元素
   * DOMUtils.siblings(document.querySelector("a.xx"))
   * DOMUtils.siblings("a.xx")
   * > (3) [div.logo-wrapper, div.forum-block, div.more-btn-desc]
   */
  DOMUtils.siblings = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    return Array.from(element.parentElement.children).filter(
      (child) => child !== element
    );
  };

  /**
   * 获取当前元素的父元素
   * @param {Element|NodeList|string} element 当前元素
   * @returns {?Element|Array} 父元素
   * @example
   * // 获取a.xx元素的父元素
   * DOMUtils.parent(document.querySelector("a.xx"))
   * DOMUtils.parent("a.xx")
   * > <div ...>....</div>
   */
  DOMUtils.parent = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (element instanceof NodeList || element instanceof Array) {
      let resultArray = [];
      element.forEach((item) => {
        resultArray = resultArray.concat(this.parent(item));
      });
      return resultArray;
    } else {
      return element.parentElement;
    }
  };

  /**
   * 将字符串转为Element元素
   * @param {string} html
   * @param {boolean} useParser 是否使用DOMParser来生成元素,有些时候通过DOMParser生成的元素有点问题
   * @param {boolean} isComplete 是否是完整的
   * @returns {Element}
   * @example
   * // 将字符串转为Element元素
   * DOMUtils.parseHTML("<a href='xxxx'></a>")
   * > <a href="xxxx"></a>
   * @example
   * // 使用DOMParser将字符串转为Element元素
   * DOMUtils.parseHTML("<a href='xxxx'></a>",true)
   * > <a href="xxxx"></a>
   * @example
   * // 由于需要转换的元素是多个元素,将字符串转为完整的Element元素
   * DOMUtils.parseHTML("<a href='xxxx'></a><a href='xxxx'></a>",false, true)
   * > <div><a href="xxxx"></a><a href='xxxx'></a></div>
   * @example
   * // 由于需要转换的元素是多个元素,使用DOMParser将字符串转为完整的Element元素
   * DOMUtils.parseHTML("<a href='xxxx'></a><a href='xxxx'></a>",true, true)
   * > #document
   */
  DOMUtils.parseHTML = function (html, useParser = false, isComplete = false) {
    function parseHTMLByDOMParser() {
      let parser = new DOMParser();
      if (isComplete) {
        return parser.parseFromString(html, "text/html");
      } else {
        return parser.parseFromString(html, "text/html").body.firstChild;
      }
    }
    function parseHTMLByCreateDom() {
      let tempDIV = document.createElement("div");
      tempDIV.innerHTML = html;
      if (isComplete) {
        return tempDIV;
      } else {
        return tempDIV.firstChild;
      }
    }
    if (useParser) {
      return parseHTMLByDOMParser();
    } else {
      return parseHTMLByCreateDom();
    }
  };

  /**
   * 当鼠标移入或移出元素时触发事件
   * @param {Element|string} element 当前元素
   * @param {Function} handler 事件处理函数
   * @example
   * // 监听a.xx元素的移入或移出
   * DOMUtils.hover(document.querySelector("a.xx"),()=>{
   *   console.log("移入/移除");
   * })
   * DOMUtils.hover("a.xx",()=>{
   *   console.log("移入/移除");
   * })
   */
  DOMUtils.hover = function (element, handler) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    DOMUtils.on(element, "mouseenter", null, handler);
    DOMUtils.on(element, "mouseleave", null, handler);
  };

  /**
   * 显示元素
   * @param {Element|string} element 当前元素
   * @example
   * // 显示a.xx元素
   * DOMUtils.show(document.querySelector("a.xx"))
   * DOMUtils.show("a.xx")
   */
  DOMUtils.show = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    element.style.display = "";
  };

  /**
   * 隐藏元素
   * @param {Element|string} element 当前元素
   * @example
   * // 隐藏a.xx元素
   * DOMUtils.hide(document.querySelector("a.xx"))
   * DOMUtils.hide("a.xx")
   */
  DOMUtils.hide = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    element.style.display = "none";
  };

  /**
   * 当按键松开时触发事件
   * @param {Element|string} element 当前元素
   * @param {Function} handler 事件处理函数
   * @example
   * // 监听a.xx元素的按键松开
   * DOMUtils.keyup(document.querySelector("a.xx"),()=>{
   *   console.log("按键松开");
   * })
   * DOMUtils.keyup("a.xx",()=>{
   *   console.log("按键松开");
   * })
   */
  DOMUtils.keyup = function (element, handler) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    DOMUtils.on(element, "keyup", null, handler);
  };

  /**
   * 当按键按下时触发事件
   * @param {Element|string} element 当前元素
   * @param {Function} handler 事件处理函数
   * @example
   * // 监听a.xx元素的按键按下
   * DOMUtils.keydown(document.querySelector("a.xx"),()=>{
   *   console.log("按键按下");
   * })
   * DOMUtils.keydown("a.xx",()=>{
   *   console.log("按键按下");
   * })
   */
  DOMUtils.keydown = function (element, handler) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    DOMUtils.on(element, "keydown", null, handler);
  };

  /**
   * 淡入元素
   * @param {Element|string} element 当前元素
   * @param {Number} [duration=400] 动画持续时间(毫秒),默认400毫秒
   * @param {Function} callback 动画结束的回调
   * @example
   * // 元素a.xx淡入
   * DOMUtils.fadeIn(document.querySelector("a.xx"),2500,()=>{
   *   console.log("淡入完毕");
   * })
   * DOMUtils.fadeIn("a.xx",undefined,()=>{
   *   console.log("淡入完毕");
   * })
   */
  DOMUtils.fadeIn = function (element, duration = 400, callback) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    element.style.opacity = 0;
    element.style.display = "";
    let start = null;
    let timer = null;
    function step(timestamp) {
      if (!start) start = timestamp;
      let progress = timestamp - start;
      element.style.opacity = Math.min(progress / duration, 1);
      if (progress < duration) {
        window.requestAnimationFrame(step);
      } else {
        if (callback && typeof callback === "function") {
          callback();
        }
        window.cancelAnimationFrame(timer);
      }
    }
    timer = window.requestAnimationFrame(step);
  };

  /**
   * 淡出元素
   * @param {Element|string} element 当前元素
   * @param {Number} [duration=400] 动画持续时间(毫秒),默认400毫秒
   * @param {Function} callback 动画结束的回调
   * @example
   * // 元素a.xx淡出
   * DOMUtils.fadeOut(document.querySelector("a.xx"),2500,()=>{
   *   console.log("淡出完毕");
   * })
   * DOMUtils.fadeOut("a.xx",undefined,()=>{
   *   console.log("淡出完毕");
   * })
   */
  DOMUtils.fadeOut = function (element, duration = 400, callback) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    element.style.opacity = 1;
    let start = null;
    let timer = null;
    function step(timestamp) {
      if (!start) start = timestamp;
      let progress = timestamp - start;
      element.style.opacity = Math.max(1 - progress / duration, 0);
      if (progress < duration) {
        window.requestAnimationFrame(step);
      } else {
        element.style.display = "none";
        if (typeof callback === "function") {
          callback();
        }
        window.cancelAnimationFrame(timer);
      }
    }
    timer = window.requestAnimationFrame(step);
  };

  /**
   * 切换元素的显示和隐藏状态
   * @param {Element|string} element 当前元素
   * @example
   * // 如果元素a.xx当前是隐藏,则显示,如果是显示,则隐藏
   * DOMUtils.toggle(document.querySelector("a.xx"))
   * DOMUtils.toggle("a.xx")
   */
  DOMUtils.toggle = function (element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if (element == null) {
      return;
    }
    if (getComputedStyle(element).getPropertyValue("display") === "none") {
      DOMUtils.show(element);
    } else {
      DOMUtils.hide(element);
    }
  };
  return DOMUtils;
});