(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMUtils = factory());
})(this, (function () { 'use strict';
class WindowApi {
/** 默认的配置 */
defaultApi = {
document: document,
window: window,
globalThis: globalThis,
self: self,
top: top,
setTimeout: globalThis.setTimeout.bind(globalThis),
setInterval: globalThis.setInterval.bind(globalThis),
clearTimeout: globalThis.clearTimeout.bind(globalThis),
clearInterval: globalThis.clearInterval.bind(globalThis),
};
/** 使用的配置 */
api;
constructor(option) {
if (option) {
if (option.globalThis == null) {
option.globalThis = option.window;
}
if (option.self == null) {
option.self = option.window;
}
}
if (!option) {
option = Object.assign({}, this.defaultApi);
}
this.api = Object.assign({}, option);
}
get document() {
return this.api.document;
}
get window() {
return this.api.window;
}
get globalThis() {
return this.api.globalThis;
}
get self() {
return this.api.self;
}
get top() {
return this.api.top;
}
get setTimeout() {
return this.api.setTimeout;
}
get clearTimeout() {
return this.api.clearTimeout;
}
get setInterval() {
return this.api.setInterval;
}
get clearInterval() {
return this.api.clearInterval;
}
}
const createCache = (lastNumberWeakMap) => {
return (collection, nextNumber) => {
lastNumberWeakMap.set(collection, nextNumber);
return nextNumber;
};
};
/*
* The value of the constant Number.MAX_SAFE_INTEGER equals (2 ** 53 - 1) but it
* is fairly new.
*/
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER === undefined ? 9007199254740991 : Number.MAX_SAFE_INTEGER;
const TWO_TO_THE_POWER_OF_TWENTY_NINE = 536870912;
const TWO_TO_THE_POWER_OF_THIRTY = TWO_TO_THE_POWER_OF_TWENTY_NINE * 2;
const createGenerateUniqueNumber = (cache, lastNumberWeakMap) => {
return (collection) => {
const lastNumber = lastNumberWeakMap.get(collection);
/*
* Let's try the cheapest algorithm first. It might fail to produce a new
* number, but it is so cheap that it is okay to take the risk. Just
* increase the last number by one or reset it to 0 if we reached the upper
* bound of SMIs (which stands for small integers). When the last number is
* unknown it is assumed that the collection contains zero based consecutive
* numbers.
*/
let nextNumber = lastNumber === undefined ? collection.size : lastNumber < TWO_TO_THE_POWER_OF_THIRTY ? lastNumber + 1 : 0;
if (!collection.has(nextNumber)) {
return cache(collection, nextNumber);
}
/*
* If there are less than half of 2 ** 30 numbers stored in the collection,
* the chance to generate a new random number in the range from 0 to 2 ** 30
* is at least 50%. It's benifitial to use only SMIs because they perform
* much better in any environment based on V8.
*/
if (collection.size < TWO_TO_THE_POWER_OF_TWENTY_NINE) {
while (collection.has(nextNumber)) {
nextNumber = Math.floor(Math.random() * TWO_TO_THE_POWER_OF_THIRTY);
}
return cache(collection, nextNumber);
}
// Quickly check if there is a theoretical chance to generate a new number.
if (collection.size > MAX_SAFE_INTEGER) {
throw new Error('Congratulations, you created a collection of unique numbers which uses all available integers!');
}
// Otherwise use the full scale of safely usable integers.
while (collection.has(nextNumber)) {
nextNumber = Math.floor(Math.random() * MAX_SAFE_INTEGER);
}
return cache(collection, nextNumber);
};
};
const LAST_NUMBER_WEAK_MAP = new WeakMap();
const cache = createCache(LAST_NUMBER_WEAK_MAP);
const generateUniqueNumber = createGenerateUniqueNumber(cache, LAST_NUMBER_WEAK_MAP);
const isMessagePort = (sender) => {
return typeof sender.start === 'function';
};
const PORT_MAP = new WeakMap();
const extendBrokerImplementation = (partialBrokerImplementation) => ({
...partialBrokerImplementation,
connect: ({ call }) => {
return async () => {
const { port1, port2 } = new MessageChannel();
const portId = await call('connect', { port: port1 }, [port1]);
PORT_MAP.set(port2, portId);
return port2;
};
},
disconnect: ({ call }) => {
return async (port) => {
const portId = PORT_MAP.get(port);
if (portId === undefined) {
throw new Error('The given port is not connected.');
}
await call('disconnect', { portId });
};
},
isSupported: ({ call }) => {
return () => call('isSupported');
}
});
const ONGOING_REQUESTS = new WeakMap();
const createOrGetOngoingRequests = (sender) => {
if (ONGOING_REQUESTS.has(sender)) {
// @todo TypeScript needs to be convinced that has() works as expected.
return ONGOING_REQUESTS.get(sender);
}
const ongoingRequests = new Map();
ONGOING_REQUESTS.set(sender, ongoingRequests);
return ongoingRequests;
};
const createBroker = (brokerImplementation) => {
const fullBrokerImplementation = extendBrokerImplementation(brokerImplementation);
return (sender) => {
const ongoingRequests = createOrGetOngoingRequests(sender);
sender.addEventListener('message', (({ data: message }) => {
const { id } = message;
if (id !== null && ongoingRequests.has(id)) {
const { reject, resolve } = ongoingRequests.get(id);
ongoingRequests.delete(id);
if (message.error === undefined) {
resolve(message.result);
}
else {
reject(new Error(message.error.message));
}
}
}));
if (isMessagePort(sender)) {
sender.start();
}
const call = (method, params = null, transferables = []) => {
return new Promise((resolve, reject) => {
const id = generateUniqueNumber(ongoingRequests);
ongoingRequests.set(id, { reject, resolve });
if (params === null) {
sender.postMessage({ id, method }, transferables);
}
else {
sender.postMessage({ id, method, params }, transferables);
}
});
};
const notify = (method, params, transferables = []) => {
sender.postMessage({ id: null, method, params }, transferables);
};
let functions = {};
for (const [key, handler] of Object.entries(fullBrokerImplementation)) {
functions = { ...functions, [key]: handler({ call, notify }) };
}
return { ...functions };
};
};
// Prefilling the Maps with a function indexed by zero is necessary to be compliant with the specification.
const scheduledIntervalsState = new Map([[0, null]]); // tslint:disable-line no-empty
const scheduledTimeoutsState = new Map([[0, null]]); // tslint:disable-line no-empty
const wrap = createBroker({
clearInterval: ({ call }) => {
return (timerId) => {
if (typeof scheduledIntervalsState.get(timerId) === 'symbol') {
scheduledIntervalsState.set(timerId, null);
call('clear', { timerId, timerType: 'interval' }).then(() => {
scheduledIntervalsState.delete(timerId);
});
}
};
},
clearTimeout: ({ call }) => {
return (timerId) => {
if (typeof scheduledTimeoutsState.get(timerId) === 'symbol') {
scheduledTimeoutsState.set(timerId, null);
call('clear', { timerId, timerType: 'timeout' }).then(() => {
scheduledTimeoutsState.delete(timerId);
});
}
};
},
setInterval: ({ call }) => {
return (func, delay = 0, ...args) => {
const symbol = Symbol();
const timerId = generateUniqueNumber(scheduledIntervalsState);
scheduledIntervalsState.set(timerId, symbol);
const schedule = () => call('set', {
delay,
now: performance.timeOrigin + performance.now(),
timerId,
timerType: 'interval'
}).then(() => {
const state = scheduledIntervalsState.get(timerId);
if (state === undefined) {
throw new Error('The timer is in an undefined state.');
}
if (state === symbol) {
func(...args);
// Doublecheck if the interval should still be rescheduled because it could have been cleared inside of func().
if (scheduledIntervalsState.get(timerId) === symbol) {
schedule();
}
}
});
schedule();
return timerId;
};
},
setTimeout: ({ call }) => {
return (func, delay = 0, ...args) => {
const symbol = Symbol();
const timerId = generateUniqueNumber(scheduledTimeoutsState);
scheduledTimeoutsState.set(timerId, symbol);
call('set', {
delay,
now: performance.timeOrigin + performance.now(),
timerId,
timerType: 'timeout'
}).then(() => {
const state = scheduledTimeoutsState.get(timerId);
if (state === undefined) {
throw new Error('The timer is in an undefined state.');
}
if (state === symbol) {
// A timeout can be savely deleted because it is only called once.
scheduledTimeoutsState.delete(timerId);
func(...args);
}
});
return timerId;
};
}
});
const load = (url) => {
const worker = new Worker(url);
return wrap(worker);
};
const createLoadOrReturnBroker = (loadBroker, worker) => {
let broker = null;
return () => {
if (broker !== null) {
return broker;
}
const blob = new Blob([worker], { type: 'application/javascript; charset=utf-8' });
const url = URL.createObjectURL(blob);
broker = loadBroker(url);
// Bug #1: Edge up until v18 didn't like the URL to be revoked directly.
setTimeout(() => URL.revokeObjectURL(url));
return broker;
};
};
// This is the minified and stringified code of the worker-timers-worker package.
const worker = `(()=>{var e={455:function(e,t){!function(e){"use strict";var t=function(e){return function(t){var r=e(t);return t.add(r),r}},r=function(e){return function(t,r){return e.set(t,r),r}},n=void 0===Number.MAX_SAFE_INTEGER?9007199254740991:Number.MAX_SAFE_INTEGER,o=536870912,s=2*o,a=function(e,t){return function(r){var a=t.get(r),i=void 0===a?r.size:a<s?a+1:0;if(!r.has(i))return e(r,i);if(r.size<o){for(;r.has(i);)i=Math.floor(Math.random()*s);return e(r,i)}if(r.size>n)throw new Error("Congratulations, you created a collection of unique numbers which uses all available integers!");for(;r.has(i);)i=Math.floor(Math.random()*n);return e(r,i)}},i=new WeakMap,u=r(i),c=a(u,i),l=t(c);e.addUniqueNumber=l,e.generateUniqueNumber=c}(t)}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var s=t[n]={exports:{}};return e[n].call(s.exports,s,s.exports,r),s.exports}(()=>{"use strict";const e=-32603,t=-32602,n=-32601,o=(e,t)=>Object.assign(new Error(e),{status:t}),s=t=>o('The handler of the method called "'.concat(t,'" returned an unexpected result.'),e),a=(t,r)=>async({data:{id:a,method:i,params:u}})=>{const c=r[i];try{if(void 0===c)throw(e=>o('The requested method called "'.concat(e,'" is not supported.'),n))(i);const r=void 0===u?c():c(u);if(void 0===r)throw(t=>o('The handler of the method called "'.concat(t,'" returned no required result.'),e))(i);const l=r instanceof Promise?await r:r;if(null===a){if(void 0!==l.result)throw s(i)}else{if(void 0===l.result)throw s(i);const{result:e,transferables:r=[]}=l;t.postMessage({id:a,result:e},r)}}catch(e){const{message:r,status:n=-32603}=e;t.postMessage({error:{code:n,message:r},id:a})}};var i=r(455);const u=new Map,c=(e,r,n)=>({...r,connect:({port:t})=>{t.start();const n=e(t,r),o=(0,i.generateUniqueNumber)(u);return u.set(o,()=>{n(),t.close(),u.delete(o)}),{result:o}},disconnect:({portId:e})=>{const r=u.get(e);if(void 0===r)throw(e=>o('The specified parameter called "portId" with the given value "'.concat(e,'" does not identify a port connected to this worker.'),t))(e);return r(),{result:null}},isSupported:async()=>{if(await new Promise(e=>{const t=new ArrayBuffer(0),{port1:r,port2:n}=new MessageChannel;r.onmessage=({data:t})=>e(null!==t),n.postMessage(t,[t])})){const e=n();return{result:e instanceof Promise?await e:e}}return{result:!1}}}),l=(e,t,r=()=>!0)=>{const n=c(l,t,r),o=a(e,n);return e.addEventListener("message",o),()=>e.removeEventListener("message",o)},d=(e,t)=>r=>{const n=t.get(r);if(void 0===n)return Promise.resolve(!1);const[o,s]=n;return e(o),t.delete(r),s(!1),Promise.resolve(!0)},f=(e,t,r,n)=>(o,s,a)=>{const i=o+s-t.timeOrigin,u=i-t.now();return new Promise(t=>{e.set(a,[r(n,u,i,e,t,a),t])})},m=new Map,h=d(globalThis.clearTimeout,m),p=new Map,v=d(globalThis.clearTimeout,p),w=((e,t)=>{const r=(n,o,s,a)=>{const i=n-e.now();i>0?o.set(a,[t(r,i,n,o,s,a),s]):(o.delete(a),s(!0))};return r})(performance,globalThis.setTimeout),g=f(m,performance,globalThis.setTimeout,w),T=f(p,performance,globalThis.setTimeout,w);l(self,{clear:async({timerId:e,timerType:t})=>({result:await("interval"===t?h(e):v(e))}),set:async({delay:e,now:t,timerId:r,timerType:n})=>({result:await("interval"===n?g:T)(e,t,r)})})})()})();`; // tslint:disable-line:max-line-length
const loadOrReturnBroker = createLoadOrReturnBroker(load, worker);
const clearInterval$1 = (timerId) => loadOrReturnBroker().clearInterval(timerId);
const clearTimeout$1 = (timerId) => loadOrReturnBroker().clearTimeout(timerId);
const setInterval$1 = (...args) => loadOrReturnBroker().setInterval(...args);
const setTimeout$1 = (...args) => loadOrReturnBroker().setTimeout(...args);
/** 通用工具类 */
const CommonUtils = {
windowApi: new WindowApi({
document: document,
window: window,
top: top,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval,
}),
/**
* 判断元素是否已显示或已连接
* @param $el
*/
isShow($el) {
return Boolean($el.getClientRects().length);
},
/**
* 创建安全的html
* @param text 字符串
*/
createSafeHTML(text) {
if (window.trustedTypes) {
const policy = window.trustedTypes.createPolicy("safe-innerHTML", {
createHTML: (html) => html,
});
return policy.createHTML(text);
}
else {
return text;
}
},
/**
* 在CSP策略下设置innerHTML
* @param $el 元素
* @param text 文本
*/
setSafeHTML($el, text) {
// 创建 TrustedHTML 策略(需 CSP 允许)
$el.innerHTML = this.createSafeHTML(text);
},
/**
* 用于强制显示元素并获取它的高度宽度等其它属性
* @param $el
*/
forceShow($el) {
const dupNode = $el.cloneNode(true);
dupNode.setAttribute("style", "visibility: hidden !important;display:block !important;");
this.windowApi.document.documentElement.appendChild(dupNode);
return {
/**
* 恢复修改的style
*/
recovery() {
dupNode.remove();
},
};
},
/**
* 获取元素上的Float格式的属性px
* @param element
* @param styleName style名
*/
getStyleValue(element, styleName) {
let view = null;
let styles = null;
if (element instanceof CSSStyleDeclaration) {
/* 直接就获取了style属性 */
styles = element;
}
else {
view = element.ownerDocument.defaultView;
if (!view || !view.opener) {
view = window;
}
styles = view.getComputedStyle(element);
}
const value = parseFloat(styles[styleName]);
if (isNaN(value)) {
return 0;
}
else {
return value;
}
},
/**
* 判断是否是window,例如window、self、globalThis
* @param target
*/
isWin(target) {
if (typeof target !== "object") {
return false;
}
if (target instanceof Node) {
return false;
}
if (target === globalThis) {
return true;
}
if (target === window) {
return true;
}
if (target === self) {
return true;
}
if (target === globalThis) {
return true;
}
if (target === window) {
return true;
}
if (target === self) {
return true;
}
if (typeof unsafeWindow !== "undefined" && target === unsafeWindow) {
return true;
}
if (target?.Math?.toString() !== "[object Math]") {
return false;
}
return true;
},
/**
* 判断对象是否是元素
* @param $el
* @returns
* + true 是元素
* + false 不是元素
* @example
* DOMUtilsCommonUtils.isDOM(document.querySelector("a"))
* > true
*/
isDOM($el) {
return $el instanceof Node;
},
/**
* 删除对象上的属性
* @param target
* @param propName
*/
delete(target, propName) {
if (typeof Reflect === "object" && Reflect != null && Reflect.deleteProperty) {
return Reflect.deleteProperty(target, propName);
}
else {
delete target[propName];
}
},
/**
* 自动使用 Worker 执行 setTimeout
*/
setTimeout(callback, timeout = 0) {
try {
return setTimeout$1(callback, timeout);
}
catch {
return this.windowApi.setTimeout(callback, timeout);
}
},
/**
* 配合 .setTimeout 使用
*/
clearTimeout(timeId) {
try {
if (timeId != null) {
clearTimeout$1(timeId);
}
}
catch {
// TODO
}
finally {
this.windowApi.clearTimeout(timeId);
}
},
/**
* 自动使用 Worker 执行 setInterval
*/
setInterval(callback, timeout = 0) {
try {
return setInterval$1(callback, timeout);
}
catch {
return this.windowApi.setInterval(callback, timeout);
}
},
/**
* 配合 .setInterval 使用
*/
clearInterval(timeId) {
try {
if (timeId != null) {
clearInterval$1(timeId);
}
}
catch {
// TODO
}
finally {
this.windowApi.clearInterval(timeId);
}
},
/**
* 判断是否是元素列表
* @param $ele
*/
isNodeList($ele) {
return Array.isArray($ele) || $ele instanceof NodeList;
},
/** 获取 animationend 在各个浏览器的兼容名 */
getAnimationEndNameList() {
return ["webkitAnimationEnd", "mozAnimationEnd", "MSAnimationEnd", "oanimationend", "animationend"];
},
/** 获取 transitionend 在各个浏览器的兼容名 */
getTransitionEndNameList() {
return ["webkitTransitionEnd", "mozTransitionEnd", "MSTransitionEnd", "otransitionend", "transitionend"];
},
};
const version = "1.7.0";
/* 数据 */
const GlobalData = {
/** .on添加在元素存储的事件 */
domEventSymbol: Symbol("events_" + (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)),
};
class ElementSelector {
windowApi;
constructor(windowApiOption) {
this.windowApi = new WindowApi(windowApiOption);
}
selector(selector, parent) {
return this.selectorAll(selector, parent)[0];
}
selectorAll(selector, parent) {
const context = this;
parent = parent || context.windowApi.document;
selector = selector.trim();
if (selector.match(/[^\s]{1}:empty$/gi)) {
// empty 语法
selector = selector.replace(/:empty$/gi, "");
return Array.from(parent.querySelectorAll(selector)).filter(($ele) => {
return $ele?.innerHTML?.trim() === "";
});
}
else if (selector.match(/[^\s]{1}:contains\("(.*)"\)$/i) || selector.match(/[^\s]{1}:contains\('(.*)'\)$/i)) {
// contains 语法
const textMatch = selector.match(/:contains\(("|')(.*)("|')\)$/i);
const text = textMatch[2];
selector = selector.replace(/:contains\(("|')(.*)("|')\)$/gi, "");
return Array.from(parent.querySelectorAll(selector)).filter(($ele) => {
// @ts-ignore
return ($ele?.textContent || $ele?.innerText)?.includes(text);
});
}
else if (selector.match(/[^\s]{1}:regexp\("(.*)"\)$/i) || selector.match(/[^\s]{1}:regexp\('(.*)'\)$/i)) {
// regexp 语法
const textMatch = selector.match(/:regexp\(("|')(.*)("|')\)$/i);
let pattern = textMatch[2];
const flagMatch = pattern.match(/("|'),[\s]*("|')([igm]{0,3})$/i);
let flags = "";
if (flagMatch) {
pattern = pattern.replace(/("|'),[\s]*("|')([igm]{0,3})$/gi, "");
flags = flagMatch[3];
}
const regexp = new RegExp(pattern, flags);
selector = selector.replace(/:regexp\(("|')(.*)("|')\)$/gi, "");
return Array.from(parent.querySelectorAll(selector)).filter(($ele) => {
// @ts-ignore
return Boolean(($ele?.textContent || $ele?.innerText)?.match(regexp));
});
}
else {
// 普通语法
return Array.from(parent.querySelectorAll(selector));
}
}
/**
* 匹配元素,可使用以下的额外语法
*
* + :contains([text]) 作用: 找到包含指定文本内容的指定元素
* + :empty 作用:找到既没有文本内容也没有子元素的指定元素
* + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素
* @param $el 元素
* @param selector 选择器
* @example
* DOMUtils.matches("div:contains('测试')")
* > true
* @example
* DOMUtils.matches("div:empty")
* > true
* @example
* DOMUtils.matches("div:regexp('^xxxx$')")
* > true
* @example
* DOMUtils.matches("div:regexp(/^xxx/ig)")
* > false
*/
matches($el, selector) {
selector = selector.trim();
if ($el == null) {
return false;
}
if (selector.match(/[^\s]{1}:empty$/gi)) {
// empty 语法
selector = selector.replace(/:empty$/gi, "");
return $el.matches(selector) && $el?.innerHTML?.trim() === "";
}
else if (selector.match(/[^\s]{1}:contains\("(.*)"\)$/i) || selector.match(/[^\s]{1}:contains\('(.*)'\)$/i)) {
// contains 语法
const textMatch = selector.match(/:contains\(("|')(.*)("|')\)$/i);
const text = textMatch[2];
selector = selector.replace(/:contains\(("|')(.*)("|')\)$/gi, "");
// @ts-ignore
let content = $el?.textContent || $el?.innerText;
if (typeof content !== "string") {
content = "";
}
return $el.matches(selector) && content?.includes(text);
}
else if (selector.match(/[^\s]{1}:regexp\("(.*)"\)$/i) || selector.match(/[^\s]{1}:regexp\('(.*)'\)$/i)) {
// regexp 语法
const textMatch = selector.match(/:regexp\(("|')(.*)("|')\)$/i);
let pattern = textMatch[2];
const flagMatch = pattern.match(/("|'),[\s]*("|')([igm]{0,3})$/i);
let flags = "";
if (flagMatch) {
pattern = pattern.replace(/("|'),[\s]*("|')([igm]{0,3})$/gi, "");
flags = flagMatch[3];
}
const regexp = new RegExp(pattern, flags);
selector = selector.replace(/:regexp\(("|')(.*)("|')\)$/gi, "");
// @ts-ignore
let content = $el?.textContent || $el?.innerText;
if (typeof content !== "string") {
content = "";
}
return $el.matches(selector) && Boolean(content?.match(regexp));
}
else {
// 普通语法
return $el.matches(selector);
}
}
closest($el, selector) {
selector = selector.trim();
if (selector.match(/[^\s]{1}:empty$/gi)) {
// empty 语法
selector = selector.replace(/:empty$/gi, "");
const $closest = $el?.closest(selector);
if ($closest && $closest?.innerHTML?.trim() === "") {
return $closest;
}
return null;
}
else if (selector.match(/[^\s]{1}:contains\("(.*)"\)$/i) || selector.match(/[^\s]{1}:contains\('(.*)'\)$/i)) {
// contains 语法
const textMatch = selector.match(/:contains\(("|')(.*)("|')\)$/i);
const text = textMatch[2];
selector = selector.replace(/:contains\(("|')(.*)("|')\)$/gi, "");
const $closest = $el?.closest(selector);
if ($closest) {
// @ts-ignore
const content = $el?.textContent || $el?.innerText;
if (typeof content === "string" && content.includes(text)) {
return $closest;
}
}
return null;
}
else if (selector.match(/[^\s]{1}:regexp\("(.*)"\)$/i) || selector.match(/[^\s]{1}:regexp\('(.*)'\)$/i)) {
// regexp 语法
const textMatch = selector.match(/:regexp\(("|')(.*)("|')\)$/i);
let pattern = textMatch[2];
const flagMatch = pattern.match(/("|'),[\s]*("|')([igm]{0,3})$/i);
let flags = "";
if (flagMatch) {
pattern = pattern.replace(/("|'),[\s]*("|')([igm]{0,3})$/gi, "");
flags = flagMatch[3];
}
const regexp = new RegExp(pattern, flags);
selector = selector.replace(/:regexp\(("|')(.*)("|')\)$/gi, "");
const $closest = $el?.closest(selector);
if ($closest) {
// @ts-ignore
const content = $el?.textContent || $el?.innerText;
if (typeof content === "string" && content.match(regexp)) {
return $closest;
}
}
return null;
}
else {
// 普通语法
const $closest = $el?.closest(selector);
return $closest;
}
}
}
const elementSelector = new ElementSelector();
/**
* 判断对象是否是元素
* @param $el
* @returns
* + true 是元素
* + false 不是元素
* @example
* DOMUtilsCommonUtils.isDOM(document.querySelector("a"))
* > true
*/
const isDOM = function ($el) {
return $el instanceof Node;
};
class Utils {
windowApi;
constructor(option) {
this.windowApi = new WindowApi(option);
}
/**
* 判断对象是否是jQuery对象
* @param target
* @returns
* + true 是jQuery对象
* + false 不是jQuery对象
* @example
* Utils.isJQuery($("a"))
* > true
*/
isJQuery(target) {
let result = false;
if (typeof jQuery === "object" && target instanceof jQuery) {
result = true;
}
if (target == null) {
return false;
}
if (typeof target === "object") {
/* 也有种可能,这个jQuery对象是1.8.3版本的,页面中的jQuery是3.4.1版本的 */
const jQueryProps = [
"add",
"addBack",
"addClass",
"after",
"ajaxComplete",
"ajaxError",
"ajaxSend",
"ajaxStart",
"ajaxStop",
"ajaxSuccess",
"animate",
"append",
"appendTo",
"attr",
"before",
"bind",
"blur",
"change",
"children",
"clearQueue",
"click",
"clone",
"closest",
"constructor",
"contents",
"contextmenu",
"css",
"data",
"dblclick",
"delay",
"delegate",
"dequeue",
"each",
"empty",
"end",
"eq",
"extend",
"fadeIn",
"fadeOut",
"fadeTo",
"fadeToggle",
"filter",
"find",
"first",
"focus",
"focusin",
"focusout",
"get",
"has",
"hasClass",
"height",
"hide",
"hover",
"html",
"index",
"init",
"innerHeight",
"innerWidth",
"insertAfter",
"insertBefore",
"is",
"jquery",
"keydown",
"keypress",
"keyup",
"last",
"load",
"map",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"next",
"nextAll",
"not",
"off",
"offset",
"offsetParent",
"on",
"one",
"outerHeight",
"outerWidth",
"parent",
"parents",
"position",
"prepend",
"prependTo",
"prev",
"prevAll",
"prevUntil",
"promise",
"prop",
"pushStack",
"queue",
"ready",
"remove",
"removeAttr",
"removeClass",
"removeData",
"removeProp",
"replaceAll",
"replaceWith",
"resize",
"scroll",
"scrollLeft",
"scrollTop",
"select",
"show",
"siblings",
"slice",
"slideDown",
"slideToggle",
"slideUp",
"sort",
"splice",
"text",
"toArray",
"toggle",
"toggleClass",
"trigger",
"triggerHandler",
"unbind",
"width",
"wrap",
];
for (const jQueryPropsName of jQueryProps) {
if (!(jQueryPropsName in target)) {
result = false;
break;
}
else {
result = true;
}
}
}
return result;
}
assign(target = {}, source = {}, isAdd = false) {
const UtilsContext = this;
if (Array.isArray(source)) {
const canTraverse = source.filter((item) => {
return typeof item === "object";
});
if (!canTraverse.length) {
return source;
}
}
if (source == null) {
return target;
}
if (target == null) {
target = {};
}
if (isAdd) {
for (const sourceKeyName in source) {
const targetKeyName = sourceKeyName;
const targetValue = Reflect.get(target, targetKeyName);
const sourceValue = Reflect.get(source, sourceKeyName);
if (typeof sourceValue === "object" && sourceValue != null && sourceKeyName in target && !isDOM(sourceValue)) {
/* 源端的值是object类型,且不是元素节点 */
Reflect.set(target, sourceKeyName, UtilsContext.assign(targetValue, sourceValue, isAdd));
continue;
}
Reflect.set(target, sourceKeyName, sourceValue);
}
}
else {
for (const targetKeyName in target) {
if (targetKeyName in source) {
const targetValue = Reflect.get(target, targetKeyName);
const sourceValue = Reflect.get(source, targetKeyName);
if (typeof sourceValue === "object" &&
sourceValue != null &&
!isDOM(sourceValue) &&
Object.keys(sourceValue).length) {
/* 源端的值是object类型,且不是元素节点 */
Reflect.set(target, targetKeyName, UtilsContext.assign(targetValue, sourceValue, isAdd));
continue;
}
/* 直接赋值 */
Reflect.set(target, targetKeyName, sourceValue);
}
}
}
return target;
}
mutationObserver(target, observer_config) {
const that = this;
const default_obverser_config = {
/* 监听到元素有反馈,需执行的函数 */
callback: () => { },
config: {
/**
* + true 监听以 target 为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target
* + false (默认) 不生效
*/
subtree: void 0,
/**
* + true 监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)
* + false (默认) 不生效
*/
childList: void 0,
/**
* + true 观察所有监听的节点属性值的变化。默认值为 true,当声明了 attributeFilter 或 attributeOldValue
* + false (默认) 不生效
*/
attributes: void 0,
/**
* 一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知
*/
attributeFilter: void 0,
/**
* + true 记录上一次被监听的节点的属性变化;可查阅 MutationObserver 中的 Monitoring attribute values 了解关于观察属性变化和属性值记录的详情
* + false (默认) 不生效
*/
attributeOldValue: void 0,
/**
* + true 监听声明的 target 节点上所有字符的变化。默认值为 true,如果声明了 characterDataOldValue
* + false (默认) 不生效
*/
characterData: void 0,
/**
* + true 记录前一个被监听的节点中发生的文本变化
* + false (默认) 不生效
*/
characterDataOldValue: void 0,
},
immediate: false,
};
observer_config = that.assign(default_obverser_config, observer_config);
const windowMutationObserver = this.windowApi.window.MutationObserver ||
this.windowApi.window.webkitMutationObserver ||
this.windowApi.window.MozMutationObserver;
// 观察者对象
const mutationObserver = new windowMutationObserver(function (mutations, observer) {
if (typeof observer_config.callback === "function") {
observer_config.callback(mutations, observer);
}
});
if (Array.isArray(target) || target instanceof NodeList) {
// 传入的是数组或者元素数组
target.forEach((item) => {
mutationObserver.observe(item, observer_config.config);
});
}
else if (that.isJQuery(target)) {
/* 传入的参数是jQuery对象 */
target.each((_, item) => {
mutationObserver.observe(item, observer_config.config);
});
}
else {
mutationObserver.observe(target, observer_config.config);
}
if (observer_config.immediate) {
/* 主动触发一次 */
if (typeof observer_config.callback === "function") {
observer_config.callback([], mutationObserver);
}
}
return mutationObserver;
}
}
const utils = new Utils();
class ElementWait extends ElementSelector {
windowApi;
constructor(windowApiOption) {
super(windowApiOption);
this.windowApi = new WindowApi(windowApiOption);
}
wait(checkFn, timeout, parent) {
const UtilsContext = this;
const __timeout__ = typeof timeout === "number" ? timeout : 0;
return new Promise((resolve) => {
const observer = utils.mutationObserver(parent || UtilsContext.windowApi.document, {
config: {
subtree: true,
childList: true,
attributes: true,
},
immediate: true,
callback(_, __observer__) {
const result = checkFn();
if (result.success) {
// 取消观察器
if (typeof __observer__?.disconnect === "function") {
__observer__.disconnect();
}
resolve(result.data);
}
},
});
if (__timeout__ > 0) {
CommonUtils.setTimeout(() => {
// 取消观察器
if (typeof observer?.disconnect === "function") {
observer.disconnect();
}
resolve(null);
}, __timeout__);
}
});
}
waitNode(...args) {
// 过滤掉undefined
args = args.filter((arg) => arg !== void 0);
const UtilsContext = this;
// 选择器
const selector = args[0];
// 父元素(监听的元素)
let parent = UtilsContext.windowApi.document;
// 超时时间
let timeout = 0;
if (typeof args[0] !== "string" && !Array.isArray(args[0]) && typeof args[0] !== "function") {
throw new TypeError("Utils.waitNode 第一个参数必须是string|string[]|Function");
}
if (args.length === 1) ;
else if (args.length === 2) {
const secondParam = args[1];
if (typeof secondParam === "number") {
// "div",10000
timeout = secondParam;
}
else if (typeof secondParam === "object" && secondParam instanceof Node) {
// "div",document
parent = secondParam;
}
else {
throw new TypeError("Utils.waitNode 第二个参数必须是number|Node");
}
}
else if (args.length === 3) {
// "div",document,10000
// 第二个参数,parent
const secondParam = args[1];
// 第三个参数,timeout
const thirdParam = args[2];
if (typeof secondParam === "object" && secondParam instanceof Node) {
parent = secondParam;
if (typeof thirdParam === "number") {
timeout = thirdParam;
}
else {
throw new TypeError("Utils.waitNode 第三个参数必须是number");
}
}
else {
throw new TypeError("Utils.waitNode 第二个参数必须是Node");
}
}
else {
throw new TypeError("Utils.waitNode 参数个数错误");
}
function getNode() {
if (Array.isArray(selector)) {
const result = [];
for (let index = 0; index < selector.length; index++) {
const node = elementSelector.selector(selector[index]);
if (node) {
result.push(node);
}
}
if (result.length === selector.length) {
return result;
}
}
else if (typeof selector === "function") {
return selector();
}
else {
return elementSelector.selector(selector, parent);
}
}
return UtilsContext.wait(() => {
const node = getNode();
if (node) {
return {
success: true,
data: node,
};
}
else {
return {
success: false,
data: node,
};
}
}, timeout, parent);
}
waitAnyNode(...args) {
// 过滤掉undefined
args = args.filter((arg) => arg !== void 0);
const UtilsContext = this;
// 选择器
const selectorList = args[0];
// 父元素(监听的元素)
let parent = UtilsContext.windowApi.document;
// 超时时间
let timeout = 0;
if (typeof args[0] !== "object" && !Array.isArray(args[0])) {
throw new TypeError("Utils.waitAnyNode 第一个参数必须是string[]");
}
if (args.length === 1) ;
else if (args.length === 2) {
const secondParam = args[1];
if (typeof secondParam === "number") {
// "div",10000
timeout = secondParam;
}
else if (typeof secondParam === "object" && secondParam instanceof Node) {
// "div",document
parent = secondParam;
}
else {
throw new TypeError("Utils.waitAnyNode 第二个参数必须是number|Node");
}
}
else if (args.length === 3) {
// "div",document,10000
// 第二个参数,parent
const secondParam = args[1];
// 第三个参数,timeout
const thirdParam = args[2];
if (typeof secondParam === "object" && secondParam instanceof Node) {
parent = secondParam;
if (typeof thirdParam === "number") {
timeout = thirdParam;
}
else {
throw new TypeError("Utils.waitAnyNode 第三个参数必须是number");
}
}
else {
throw new TypeError("Utils.waitAnyNode 第二个参数必须是Node");
}
}
else {
throw new TypeError("Utils.waitAnyNode 参数个数错误");
}
const promiseList = selectorList.map((selector) => {
return UtilsContext.waitNode(selector, parent, timeout);
});
return Promise.any(promiseList);
}
waitNodeList(...args) {
// 过滤掉undefined
args = args.filter((arg) => arg !== void 0);
const UtilsContext = this;
// 选择器数组
const selector = args[0];
// 父元素(监听的元素)
let parent = UtilsContext.windowApi.document;
// 超时时间
let timeout = 0;
if (typeof args[0] !== "string" && !Array.isArray(args[0])) {
throw new TypeError("Utils.waitNodeList 第一个参数必须是string|string[]");
}
if (args.length === 1) ;
else if (args.length === 2) {
const secondParam = args[1];
if (typeof secondParam === "number") {
// "div",10000
timeout = secondParam;
}
else if (typeof secondParam === "object" && secondParam instanceof Node) {
// "div",document
parent = secondParam;
}
else {
throw new TypeError("Utils.waitNodeList 第二个参数必须是number|Node");
}
}
else if (args.length === 3) {
// "div",document,10000
// 第二个参数,parent
const secondParam = args[1];
// 第三个参数,timeout
const thirdParam = args[2];
if (typeof secondParam === "object" && secondParam instanceof Node) {
parent = secondParam;
if (typeof thirdParam === "number") {
timeout = thirdParam;
}
else {
throw new TypeError("Utils.waitNodeList 第三个参数必须是number");
}
}
else {
throw new TypeError("Utils.waitNodeList 第二个参数必须是Node");
}
}
else {
throw new TypeError("Utils.waitNodeList 参数个数错误");
}
function getNodeList() {
if (Array.isArray(selector)) {
const result = [];
for (let index = 0; index < selector.length; index++) {
const nodeList = elementSelector.selectorAll(selector[index], parent);
if (nodeList.length) {
result.push(nodeList);
}
}
if (result.length === selector.length) {
return result;
}
}
else {
const nodeList = elementSelector.selectorAll(selector, parent);
if (nodeList.length) {
return nodeList;
}
}
}
return UtilsContext.wait(() => {
const node = getNodeList();
if (node) {
return {
success: true,
data: node,
};
}
else {
return {
success: false,
data: node,
};
}
}, timeout, parent);
}
waitAnyNodeList(...args) {
// 过滤掉undefined
args = args.filter((arg) => arg !== void 0);
const UtilsContext = this;
// 选择器数组
const selectorList = args[0];
// 父元素(监听的元素)
let parent = UtilsContext.windowApi.document;
// 超时时间
let timeout = 0;
if (!Array.isArray(args[0])) {
throw new TypeError("Utils.waitAnyNodeList 第一个参数必须是string[]");
}
if (args.length === 1) ;
else if (args.length === 2) {
const secondParam = args[1];
if (typeof secondParam === "number") {
// "div",10000
timeout = secondParam;
}
else if (typeof secondParam === "object" && secondParam instanceof Node) {
// "div",document
parent = secondParam;
}
else {
throw new TypeError("Utils.waitAnyNodeList 第二个参数必须是number|Node");
}
}
else if (args.length === 3) {
// "div",document,10000
// 第二个参数,parent
const secondParam = args[1];
// 第三个参数,timeout
const thirdParam = args[2];
if (typeof secondParam === "object" && secondParam instanceof Node) {
parent = secondParam;
if (typeof thirdParam === "number") {
timeout = thirdParam;
}
else {
throw new TypeError("Utils.waitAnyNodeList 第三个参数必须是number");
}
}
else {
throw new TypeError("Utils.waitAnyNodeList 第二个参数必须是Node");
}
}
else {
throw new TypeError("Utils.waitAnyNodeList 参数个数错误");
}
const promiseList = selectorList.map((selector) => {
return UtilsContext.waitNodeList(selector, parent, timeout);
});
return Promise.any(promiseList);
}
}
new ElementWait();
class ElementAnimate extends ElementWait {
windowApi;
constructor(windowApiOption) {
super(windowApiOption);
this.windowApi = new WindowApi(windowApiOption);
}
/**
* 在一定时间内改变元素的样式属性,实现动画效果
* @param element 需要进行动画的元素
* @param styles 动画结束时元素的样式属性
* @param duration 动画持续时间,单位为毫秒
* @param callback 动画结束后执行的函数
* @example
* // 监听元素a.xx的从显示变为隐藏
* DOMUtils.animate(document.querySelector("a.xx"),{ top:100},1000,function(){
* console.log("已往上位移100px")
* })
*/
animate(element, styles, duration = 1000, callback = null) {
const context = this;
if (typeof element === "string") {
element = elementSelector.selectorAll(element);
}
if (element == null) {
return;
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
context.animate($ele, styles, duration, callback);
});
return;
}
if (typeof duration !== "number" || duration <= 0) {
throw new TypeError("duration must be a positive number");
}
if (typeof callback !== "function" && callback !== void 0) {
throw new TypeError("callback must be a function or null");
}
if (typeof styles !== "object" || styles === void 0) {
throw new TypeError("styles must be an object");
}
if (Object.keys(styles).length === 0) {
throw new Error("styles must contain at least one property");
}
const start = performance.now();
const from = {};
const to = {};
for (const prop in styles) {
from[prop] = element.style[prop] || context.windowApi.globalThis.getComputedStyle(element)[prop];
to[prop] = styles[prop];
}
const timer = CommonUtils.setInterval(function () {
const timePassed = performance.now() - start;
let progress = timePassed / duration;
if (progress > 1) {
progress = 1;
}
for (const prop in styles) {
element.style[prop] = from[prop] + (to[prop] - from[prop]) * progress + "px";
}
if (progress === 1) {
CommonUtils.clearInterval(timer);
if (callback) {
callback();
}
}
}, 10);
}
/**
* 显示元素
* @param target 当前元素
* @param checkVisiblie 是否检测元素是否显示
* + true (默认)如果检测到还未显示,则强制使用display: unset !important;
* + false 不检测,直接设置display属性为空
* @example
* // 显示a.xx元素
* DOMUtils.show(document.querySelector("a.xx"))
* DOMUtils.show(document.querySelectorAll("a.xx"))
* DOMUtils.show("a.xx")
*/
show(target, checkVisiblie = true) {
const context = this;
if (target == null) {
return;
}
if (typeof target === "string") {
target = elementSelector.selectorAll(target);
}
if (target instanceof NodeList || target instanceof Array) {
target = target;
for (const element of target) {
context.show(element, checkVisiblie);
}
}
else {
target = target;
target.style.display = "";
if (checkVisiblie) {
if (!CommonUtils.isShow(target)) {
/* 仍然是不显示,尝试使用强覆盖 */
target.style.setProperty("display", "unset", "important");
}
}
}
}
/**
* 隐藏元素
* @param target 当前元素
* @param checkVisiblie 是否检测元素是否显示
* + true (默认)如果检测到显示,则强制使用display: none !important;
* + false 不检测,直接设置display属性为none
* @example
* // 隐藏a.xx元素
* DOMUtils.hide(document.querySelector("a.xx"))
* DOMUtils.hide(document.querySelectorAll("a.xx"))
* DOMUtils.hide("a.xx")
*/
hide(target, checkVisiblie = true) {
const context = this;
if (target == null) {
return;
}
if (typeof target === "string") {
target = elementSelector.selectorAll(target);
}
if (target instanceof NodeList || target instanceof Array) {
target = target;
for (const element of target) {
context.hide(element, checkVisiblie);
}
}
else {
target = target;
target.style.display = "none";
if (checkVisiblie) {
if (CommonUtils.isShow(target)) {
/* 仍然是显示,尝试使用强覆盖 */
target.style.setProperty("display", "none", "important");
}
}
}
}
/**
* 淡入元素
* @param element 当前元素
* @param duration 动画持续时间(毫秒),默认400毫秒
* @param callback 动画结束的回调
* @example
* // 元素a.xx淡入
* DOMUtils.fadeIn(document.querySelector("a.xx"),2500,()=>{
* console.log("淡入完毕");
* })
* DOMUtils.fadeIn("a.xx",undefined,()=>{
* console.log("淡入完毕");
* })
*/
fadeIn(element, duration = 400, callback) {
if (element == null) {
return;
}
const context = this;
if (typeof element === "string") {
element = elementSelector.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
context.fadeIn($ele, duration, callback);
});
return;
}
element.style.opacity = "0";
element.style.display = "";
let start = null;
let timer = null;
function step(timestamp) {
if (!start)
start = timestamp;
const progress = timestamp - start;
element = element;
element.style.opacity = Math.min(progress / duration, 1).toString();
if (progress < duration) {
context.windowApi.window.requestAnimationFrame(step);
}
else {
if (callback && typeof callback === "function") {
callback();
}
context.windowApi.window.cancelAnimationFrame(timer);
}
}
timer = context.windowApi.window.requestAnimationFrame(step);
}
/**
* 淡出元素
* @param element 当前元素
* @param duration 动画持续时间(毫秒),默认400毫秒
* @param callback 动画结束的回调
* @example
* // 元素a.xx淡出
* DOMUtils.fadeOut(document.querySelector("a.xx"),2500,()=>{
* console.log("淡出完毕");
* })
* DOMUtils.fadeOut("a.xx",undefined,()=>{
* console.log("淡出完毕");
* })
*/
fadeOut(element, duration = 400, callback) {
const context = this;
if (element == null) {
return;
}
if (typeof element === "string") {
element = elementSelector.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
context.fadeOut($ele, duration, callback);
});
return;
}
element.style.opacity = "1";
let start = null;
let timer = null;
function step(timestamp) {
if (!start)
start = timestamp;
const progress = timestamp - start;
element = element;
element.style.opacity = Math.max(1 - progress / duration, 0).toString();
if (progress < duration) {
context.windowApi.window.requestAnimationFrame(step);
}
else {
element.style.display = "none";
if (typeof callback === "function") {
callback();
}
context.windowApi.window.cancelAnimationFrame(timer);
}
}
timer = context.windowApi.window.requestAnimationFrame(step);
}
/**
* 切换元素的显示和隐藏状态
* @param element 当前元素
* @param checkVisiblie 是否检测元素是否显示
* @example
* // 如果元素a.xx当前是隐藏,则显示,如果是显示,则隐藏
* DOMUtils.toggle(document.querySelector("a.xx"))
* DOMUtils.toggle("a.xx")
*/
toggle(element, checkVisiblie) {
const context = this;
if (typeof element === "string") {
element = elementSelector.selectorAll(element);
}
if (element == null) {
return;
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
context.toggle($ele);
});
return;
}
if (context.windowApi.globalThis.getComputedStyle(element).getPropertyValue("display") === "none") {
context.show(element, checkVisiblie);
}
else {
context.hide(element, checkVisiblie);
}
}
}
new ElementAnimate();
const OriginPrototype = {
Object: {
defineProperty: Object.defineProperty,
},
};
class ElementEvent extends ElementAnimate {
windowApi;
constructor(windowApiOption) {
super(windowApiOption);
this.windowApi = new WindowApi(windowApiOption);
}
/** 获取 animationend 在各个浏览器的兼容名 */
getAnimationEndNameList() {
return CommonUtils.getAnimationEndNameList();
}
/** 获取 transitionend 在各个浏览器的兼容名 */
getTransitionEndNameList() {
return CommonUtils.getTransitionEndNameList();
}
on(element, eventType, selector, callback, option) {
/**
* 获取option配置
* @param args
* @param startIndex
* @param option
*/
function getOption(args, startIndex, option) {
const currentParam = args[startIndex];
if (typeof currentParam === "boolean") {
option.capture = currentParam;
if (typeof args[startIndex + 1] === "boolean") {
option.once = args[startIndex + 1];
}
if (typeof args[startIndex + 2] === "boolean") {
option.passive = args[startIndex + 2];
}
}
else if (typeof currentParam === "object" &&
("capture" in currentParam ||
"once" in currentParam ||
"passive" in currentParam ||
"isComposedPath" in currentParam)) {
option.capture = currentParam.capture;
option.once = currentParam.once;
option.passive = currentParam.passive;
option.isComposedPath = currentParam.isComposedPath;
}
return option;
}
const that = this;
// eslint-disable-next-line prefer-rest-params
const args = arguments;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let elementList = [];
if (element instanceof NodeList || Array.isArray(element)) {
element = element;
elementList = [...element];
}
else {
elementList.push(element);
}
// 事件名
let eventTypeList = [];
if (Array.isArray(eventType)) {
eventTypeList = eventTypeList.concat(eventType.filter((eventTypeItem) => typeof eventTypeItem === "string" && eventTypeItem.toString() !== ""));
}
else if (typeof eventType === "string") {
eventTypeList = eventTypeList.concat(eventType.split(" ").filter((eventTypeItem) => eventTypeItem !== ""));
}
// 子元素选择器
let selectorList = [];
if (Array.isArray(selector)) {
selectorList = selectorList.concat(selector.filter((selectorItem) => typeof selectorItem === "string" && selectorItem.toString() !== ""));
}
else if (typeof selector === "string") {
selectorList.push(selector);
}
// 事件回调
let listenerCallBack = callback;
// 事件配置
let listenerOption = {
capture: false,
once: false,
passive: false,
isComposedPath: false,
};
if (typeof selector === "function") {
// 这是为没有selector的情况
// 那么它就是callback
listenerCallBack = selector;
listenerOption = getOption(args, 3, listenerOption);
}
else {
// 这是存在selector的情况
listenerOption = getOption(args, 4, listenerOption);
}
/**
* 如果是once,那么删除该监听和元素上的事件和监听
*/
function checkOptionOnceToRemoveEventListener() {
if (listenerOption.once) {
that.off(element, eventType, selector, callback, option);
}
}
elementList.forEach((elementItem) => {
/**
* 事件回调
* @param event
*/
function domUtilsEventCallBack(event) {
if (selectorList.length) {
/* 存在子元素选择器 */
// 这时候的this和target都是子元素选择器的元素
let eventTarget = listenerOption.isComposedPath
? event.composedPath()[0]
: event.target;
let totalParent = elementItem;
if (CommonUtils.isWin(totalParent)) {
if (totalParent === that.windowApi.document) {
totalParent = that.windowApi.document.documentElement;
}
}
const findValue = selectorList.find((selectorItem) => {
// 判断目标元素是否匹配选择器
if (that.matches(eventTarget, selectorItem)) {
/* 当前目标可以被selector所匹配到 */
return true;
}
/* 在上层与主元素之间寻找可以被selector所匹配到的 */
const $closestMatches = that.closest(eventTarget, selectorItem);
if ($closestMatches && totalParent?.contains($closestMatches)) {
eventTarget = $closestMatches;
return true;
}
return false;
});
if (findValue) {
// 这里尝试使用defineProperty修改event的target值
try {
OriginPrototype.Object.defineProperty(event, "target", {
get() {
return eventTarget;
},
});
}
catch {
// TODO
}
listenerCallBack.call(eventTarget, event, eventTarget);
checkOptionOnceToRemoveEventListener();
}
}
else {
// 这时候的this指向监听的元素
listenerCallBack.call(elementItem, event);
checkOptionOnceToRemoveEventListener();
}
}
/* 遍历事件名设置元素事件 */
eventTypeList.forEach((eventName) => {
elementItem.addEventListener(eventName, domUtilsEventCallBack, listenerOption);
/* 获取对象上的事件 */
const elementEvents = Reflect.get(elementItem, GlobalData.domEventSymbol) || {};
/* 初始化对象上的xx事件 */
elementEvents[eventName] = elementEvents[eventName] || [];
elementEvents[eventName].push({
selector: selectorList,
option: listenerOption,
callback: domUtilsEventCallBack,
originCallBack: listenerCallBack,
});
/* 覆盖事件 */
Reflect.set(elementItem, GlobalData.domEventSymbol, elementEvents);
});
});
}
off(element, eventType, selector, callback, option, filter) {
/**
* 获取option配置
* @param args1
* @param startIndex
* @param option
*/
function getOption(args1, startIndex, option) {
const currentParam = args1[startIndex];
if (typeof currentParam === "boolean") {
option.capture = currentParam;
}
else if (typeof currentParam === "object" && currentParam != null && "capture" in currentParam) {
option.capture = currentParam.capture;
}
return option;
}
const that = this;
// eslint-disable-next-line prefer-rest-params
const args = arguments;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let $elList = [];
if (element instanceof NodeList || Array.isArray(element)) {
element = element;
$elList = [...element];
}
else {
$elList.push(element);
}
let eventTypeList = [];
if (Array.isArray(eventType)) {
eventTypeList = eventTypeList.concat(eventType.filter((eventTypeItem) => typeof eventTypeItem === "string" && eventTypeItem.toString() !== ""));
}
else if (typeof eventType === "string") {
eventTypeList = eventTypeList.concat(eventType.split(" ").filter((eventTypeItem) => eventTypeItem !== ""));
}
// 子元素选择器
let selectorList = [];
if (Array.isArray(selector)) {
selectorList = selectorList.concat(selector.filter((selectorItem) => typeof selectorItem === "string" && selectorItem.toString() !== ""));
}
else if (typeof selector === "string") {
selectorList.push(selector);
}
/**
* 事件的回调函数
*/
let listenerCallBack = callback;
/**
* 事件的配置
*/
let listenerOption = {
capture: false,
};
if (typeof selector === "function") {
// 这是为没有selector的情况
// 那么它就是callback
listenerCallBack = selector;
listenerOption = getOption(args, 3, listenerOption);
}
else {
// 这是存在selector的情况
listenerOption = getOption(args, 4, listenerOption);
}
// 是否移除所有事件
let isRemoveAll = false;
if (args.length === 2) {
// 目标函数、事件名
isRemoveAll = true;
}
else if ((args.length === 3 && typeof args[2] === "string") || Array.isArray(args[2])) {
// 目标函数、事件名、子元素选择器
isRemoveAll = true;
}
if (args.length === 5 && typeof args[4] === "function" && typeof filter !== "function") {
// 目标函数、事件名、回调函数、事件配置、过滤函数
filter = option;
}
$elList.forEach(($elItem) => {
/* 获取对象上的事件 */
const elementEvents = Reflect.get($elItem, GlobalData.domEventSymbol) || {};
eventTypeList.forEach((eventName) => {
const handlers = elementEvents[eventName] || [];
const filterHandler = typeof filter === "function" ? handlers.filter(filter) : handlers;
for (let index = 0; index < filterHandler.length; index++) {
const handler = filterHandler[index];
let flag = true;
if (flag && listenerCallBack && handler.originCallBack !== listenerCallBack) {
// callback不同
flag = false;
}
if (flag && selectorList.length && Array.isArray(handler.selector)) {
if (JSON.stringify(handler.selector) !== JSON.stringify(selectorList)) {
// 子元素选择器不同
flag = false;
}
}
if (flag &&
typeof handler.option.capture === "boolean" &&
listenerOption.capture !== handler.option.capture) {
// 事件的配置项不同
flag = false;
}
if (flag || isRemoveAll) {
$elItem.removeEventListener(eventName, handler.callback, handler.option);
const findIndex = handlers.findIndex((item) => item === handler);
if (findIndex !== -1) {
handlers.splice(findIndex, 1);
}
}
}
if (handlers.length === 0) {
/* 如果没有任意的handler,那么删除该属性 */
CommonUtils.delete(elementEvents, eventType);
}
});
Reflect.set($elItem, GlobalData.domEventSymbol, elementEvents);
});
}
/**
* 取消绑定所有的事件
* @param element 需要取消绑定的元素|元素数组
* @param eventType (可选)需要取消监听的事件
*/
offAll(element, eventType) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let $elList = [];
if (element instanceof NodeList || Array.isArray(element)) {
$elList = [...element];
}
else {
$elList.push(element);
}
let eventTypeList = [];
if (Array.isArray(eventType)) {
eventTypeList = eventTypeList.concat(eventType);
}
else if (typeof eventType === "string") {
eventTypeList = eventTypeList.concat(eventType.split(" "));
}
$elList.forEach(($elItem) => {
const symbolList = [...new Set([...Object.getOwnPropertySymbols($elItem), GlobalData.domEventSymbol])];
symbolList.forEach((symbolItem) => {
if (!symbolItem.toString().startsWith("Symbol(events_")) {
return;
}
const elementEvents = Reflect.get($elItem, symbolItem) || {};
const iterEventNameList = eventTypeList.length ? eventTypeList : Object.keys(elementEvents);
iterEventNameList.forEach((eventName) => {
const handlers = elementEvents[eventName];
if (!handlers) {
return;
}
for (const handler of handlers) {
$elItem.removeEventListener(eventName, handler.callback, {
capture: handler["option"]["capture"],
});
}
const events = Reflect.get($elItem, symbolItem);
CommonUtils.delete(events, eventName);
});
});
});
}
/**
* 等待文档加载完成后执行指定的函数
* @param callback 需要执行的函数
* @example
* DOMUtils.ready(function(){
* console.log("文档加载完毕")
* })
*/
ready(callback) {
if (typeof callback !== "function") {
return;
}
const that = this;
/**
* 检测文档是否加载完毕
*/
function checkDOMReadyState() {
try {
if (that.windowApi.document.readyState === "complete" ||
(that.windowApi.document.readyState !== "loading" &&
!that.windowApi.document.documentElement.doScroll)) {
return true;
}
else {
return false;
}
}
catch {
return false;
}
}
/**
* 成功加载完毕后触发的回调函数
*/
function completed() {
removeDomReadyListener();
callback();
}
const targetList = [
{
target: that.windowApi.document,
eventType: "DOMContentLoaded",
callback: completed,
},
{
target: that.windowApi.window,
eventType: "load",
callback: completed,
},
];
/**
* 添加监听
*/
function addDomReadyListener() {
for (let index = 0; index < targetList.length; index++) {
const item = targetList[index];
item.target.addEventListener(item.eventType, item.callback);
}
}
/**
* 移除监听
*/
function removeDomReadyListener() {
for (let index = 0; index < targetList.length; index++) {
const item = targetList[index];
item.target.removeEventListener(item.eventType, item.callback);
}
}
if (checkDOMReadyState()) {
/* 检查document状态 */
CommonUtils.setTimeout(callback);
}
else {
/* 添加监听 */
addDomReadyListener();
}
}
/**
* 主动触发事件
* @param element 需要触发的元素|元素数组|window
* @param eventType 需要触发的事件
* @param details 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象
* @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true
* @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"])
*/
trigger(element, eventType, details, useDispatchToTriggerEvent = true) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
let $elList = [];
if (element instanceof NodeList || Array.isArray(element)) {
element = element;
$elList = [...element];
}
else {
$elList = [element];
}
let eventTypeList = [];
if (Array.isArray(eventType)) {
eventTypeList = eventType;
}
else if (typeof eventType === "string") {
eventTypeList = eventType.split(" ");
}
$elList.forEach(($elItem) => {
/* 获取对象上的事件 */
const elementEvents = Reflect.get($elItem, GlobalData.domEventSymbol) || {};
eventTypeList.forEach((__eventType) => {
let event = null;
if (details && details instanceof Event) {
event = details;
}
else {
// 构造事件
event = new Event(__eventType);
if (details) {
Object.keys(details).forEach((keyName) => {
const value = Reflect.get(details, keyName);
// 在event上添加属性
Reflect.set(event, keyName, value);
});
}
}
if (useDispatchToTriggerEvent == false && __eventType in elementEvents) {
// 直接调用监听的事件
elementEvents[__eventType].forEach((eventsItem) => {
eventsItem.callback(event);
});
}
else {
$elItem.dispatchEvent(event);
}
});
});
}
/**
* 绑定或触发元素的click事件
* @param element 目标元素
* @param handler (可选)事件处理函数
* @param details (可选)赋予触发的Event的额外属性
* @param useDispatchToTriggerEvent (可选)是否使用dispatchEvent来触发事件,默认true
* @example
* // 触发元素a.xx的click事件
* DOMUtils.click(document.querySelector("a.xx"))
* DOMUtils.click("a.xx")
* DOMUtils.click("a.xx",function(){
* console.log("触发click事件成功")
* })
* */
click(element, handler, details, useDispatchToTriggerEvent) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.click($ele, handler, details, useDispatchToTriggerEvent);
});
return;
}
if (handler == null) {
that.trigger(element, "click", details, useDispatchToTriggerEvent);
}
else {
that.on(element, "click", null, handler);
}
}
/**
* 绑定或触发元素的blur事件
* @param element 目标元素
* @param handler (可选)事件处理函数
* @param details (可选)赋予触发的Event的额外属性
* @param useDispatchToTriggerEvent (可选)是否使用dispatchEvent来触发事件,默认true
* @example
* // 触发元素a.xx的blur事件
* DOMUtils.blur(document.querySelector("a.xx"))
* DOMUtils.blur("a.xx")
* DOMUtils.blur("a.xx",function(){
* console.log("触发blur事件成功")
* })
* */
blur(element, handler, details, useDispatchToTriggerEvent) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.focus($ele, handler, details, useDispatchToTriggerEvent);
});
return;
}
if (handler === null) {
that.trigger(element, "blur", details, useDispatchToTriggerEvent);
}
else {
that.on(element, "blur", null, handler);
}
}
/**
* 绑定或触发元素的focus事件
* @param element 目标元素
* @param handler (可选)事件处理函数
* @param details (可选)赋予触发的Event的额外属性
* @param useDispatchToTriggerEvent (可选)是否使用dispatchEvent来触发事件,默认true
* @example
* // 触发元素a.xx的focus事件
* DOMUtils.focus(document.querySelector("a.xx"))
* DOMUtils.focus("a.xx")
* DOMUtils.focus("a.xx",function(){
* console.log("触发focus事件成功")
* })
* */
focus(element, handler, details, useDispatchToTriggerEvent) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.focus($ele, handler, details, useDispatchToTriggerEvent);
});
return;
}
if (handler == null) {
that.trigger(element, "focus", details, useDispatchToTriggerEvent);
}
else {
that.on(element, "focus", null, handler);
}
}
/**
* 当鼠标移入或移出元素时触发事件
* @param element 当前元素
* @param handler 事件处理函数
* @param option 配置
* @example
* // 监听a.xx元素的移入或移出
* DOMUtils.hover(document.querySelector("a.xx"),()=>{
* console.log("移入/移除");
* })
* DOMUtils.hover("a.xx",()=>{
* console.log("移入/移除");
* })
*/
hover(element, handler, option) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (element == null) {
return;
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.hover($ele, handler, option);
});
return;
}
that.on(element, "mouseenter", null, handler, option);
that.on(element, "mouseleave", null, handler, option);
}
/**
* 当动画结束时触发事件
* @param element 监听的元素
* @param handler 触发的回调函数
* @param option 配置项,这里默认配置once为true
*/
animationend(element, handler, option) {
const that = this;
if (typeof element === "string") {
element = that.selector(element);
}
if (element == null) {
return;
}
const defaultOption = {
once: true,
};
Object.assign(defaultOption, option || {});
const eventNameList = CommonUtils.getAnimationEndNameList();
that.on(element, eventNameList, null, handler, defaultOption);
if (!defaultOption.once) {
return {
off() {
that.off(element, eventNameList, null, handler, defaultOption);
},
};
}
}
/**
* 当过渡结束时触发事件
* @param element 监听的元素
* @param handler 触发的回调函数
* @param option 配置项,这里默认配置once为true
*/
transitionend(element, handler, option) {
const that = this;
if (typeof element === "string") {
element = that.selector(element);
}
if (element == null) {
return;
}
const defaultOption = {
once: true,
};
Object.assign(defaultOption, option || {});
const eventNameList = CommonUtils.getTransitionEndNameList();
that.on(element, eventNameList, null, handler, defaultOption);
if (!defaultOption.once) {
return {
off() {
that.off(element, eventNameList, null, handler, defaultOption);
},
};
}
}
/**
* 当按键松开时触发事件
* keydown - > keypress - > keyup
* @param element 当前元素
* @param handler 事件处理函数
* @param option 配置
* @example
* // 监听a.xx元素的按键松开
* DOMUtils.keyup(document.querySelector("a.xx"),()=>{
* console.log("按键松开");
* })
* DOMUtils.keyup("a.xx",()=>{
* console.log("按键松开");
* })
*/
keyup(element, handler, option) {
const that = this;
if (element == null) {
return;
}
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.keyup($ele, handler, option);
});
return;
}
that.on(element, "keyup", null, handler, option);
}
/**
* 当按键按下时触发事件
* keydown - > keypress - > keyup
* @param element 目标
* @param handler 事件处理函数
* @param option 配置
* @example
* // 监听a.xx元素的按键按下
* DOMUtils.keydown(document.querySelector("a.xx"),()=>{
* console.log("按键按下");
* })
* DOMUtils.keydown("a.xx",()=>{
* console.log("按键按下");
* })
*/
keydown(element, handler, option) {
const that = this;
if (element == null) {
return;
}
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.keydown($ele, handler, option);
});
return;
}
that.on(element, "keydown", null, handler, option);
}
/**
* 当按键按下时触发事件
* keydown - > keypress - > keyup
* @param element 目标
* @param handler 事件处理函数
* @param option 配置
* @example
* // 监听a.xx元素的按键按下
* DOMUtils.keypress(document.querySelector("a.xx"),()=>{
* console.log("按键按下");
* })
* DOMUtils.keypress("a.xx",()=>{
* console.log("按键按下");
* })
*/
keypress(element, handler, option) {
const that = this;
if (element == null) {
return;
}
if (typeof element === "string") {
element = that.selectorAll(element);
}
if (CommonUtils.isNodeList(element)) {
// 设置
element.forEach(($ele) => {
that.keypress($ele, handler, option);
});
return;
}
that.on(element, "keypress", null, handler, option);
}
/**
* 监听某个元素键盘按键事件或window全局按键事件
* 按下有值的键时触发,按下Ctrl\Alt\Shift\Meta是无值键。按下先触发keydown事件,再触发keypress事件。
* @param element 需要监听的对象,可以是全局Window或者某个元素
* @param eventName 事件名,默认keypress
* @param callback 自己定义的回调事件,参数1为当前的key,参数2为组合按键,数组类型,包含ctrl、shift、alt和meta(win键或mac的cmd键)
* @param options 监听事件的配置
* @example
Utils.listenKeyboard(window,(keyName,keyValue,otherKey,event)=>{
if(keyName === "Enter"){
console.log("回车按键的值是:"+keyValue)
}
if(otherKey.indexOf("ctrl") && keyName === "Enter" ){
console.log("Ctrl和回车键");
}
})
* @example
字母和数字键的键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
A 65 J 74 S 83 1 49
B 66 K 75 T 84 2 50
C 67 L 76 U 85 3 51
D 68 M 77 V 86 4 52
E 69 N 78 W 87 5 53
F 70 O 79 X 88 6 54
G 71 P 80 Y 89 7 55
H 72 Q 81 Z 90 8 56
I 73 R 82 0 48 9 57
数字键盘上的键的键码值(keyCode)
功能键键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
0 96 8 104 F1 112 F7 118
1 97 9 105 F2 113 F8 119
2 98 * 106 F3 114 F9 120
3 99 + 107 F4 115 F10 121
4 100 Enter 108 F5 116 F11 122
5 101 - 109 F6 117 F12 123
6 102 . 110
7 103 / 111
控制键键码值(keyCode)
按键 键码 按键 键码 按键 键码 按键 键码
BackSpace 8 Esc 27 → 39 -_ 189
Tab 9 Spacebar 32 ↓ 40 .> 190
Clear 12 Page Up 33 Insert 45 /? 191
Enter 13 Page Down 34 Delete 46 `~ 192
Shift 16 End 35 Num Lock 144 [{ 219
Control 17 Home 36 ;: 186 \| 220
Alt 18 ← 37 =+ 187 ]} 221
Cape Lock 20 ↑ 38 ,< 188 '" 222
多媒体键码值(keyCode)
按键 键码
音量加 175
音量减 174
停止 179
静音 173
浏览器 172
邮件 180
搜索 170
收藏 171
**/
listenKeyboard(element, eventName = "keypress", callback, options) {
const that = this;
if (typeof element === "string") {
element = that.selectorAll(element);
}
const keyboardEventCallBack = function (event) {
/** 键名 */
const keyName = event.key || event.code;
/** 键值 */
const keyValue = event.charCode || event.keyCode || event.which;
/** 组合键列表 */
const otherCodeList = [];
if (event.ctrlKey) {
otherCodeList.push("ctrl");
}
if (event.altKey) {
otherCodeList.push("alt");
}
if (event.metaKey) {
otherCodeList.push("meta");
}
if (event.shiftKey) {
otherCodeList.push("shift");
}
if (typeof callback === "function") {
callback(keyName, keyValue, otherCodeList, event);
}
};
that.on(element, eventName, keyboardEventCallBack, options);
return {
removeListen: () => {
that.off(element, eventName, keyboardEventCallBack, options);
},
};
}
preventEvent(...args) {
/**
* 阻止事件的默认行为发生,并阻止事件传播
*/
const stopEvent = (event) => {
/* 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL */
event?.preventDefault();
/* 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素 */
event?.stopPropagation();
/* 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发 */
event?.stopImmediatePropagation();
return false;
};
if (args.length === 1) {
/* 直接阻止事件 */
return stopEvent(args[0]);
}
else {
const $el = args[0];
let eventNameList = args[1];
const capture = args[2];
/* 添加对应的事件来阻止触发 */
if (typeof eventNameList === "string") {
eventNameList = [eventNameList];
}
this.on($el, eventNameList, stopEvent, { capture: Boolean(capture) });
}
}
}
new ElementEvent();
class ElementHandler extends ElementEvent {
windowApi;
constructor(windowApiOption) {
super(windowApiOption);
this.windowApi = new WindowApi(windowApiOption);
}
/**
* 获取元素的选择器字符串
* @param $el
* @example
* DOMUtils.getElementSelector(document.querySelector("a"))
* > '.....'
*/
getElementSelector($el) {
const that = this;
if (!$el)
return void 0;
if (!$el.parentElement)
return void 0;
/* 如果元素有id属性,则直接返回id选择器 */
if ($el.id)
return `#${$el.id}`;
/* 递归地获取父元素的选择器 */
let selector = that.getElementSelector($el.parentElement);
if (!selector) {
return $el.tagName.toLowerCase();
}
/* 如果有多个相同类型的兄弟元素,则需要添加索引 */
if ($el.parentElement.querySelectorAll($el.tagName).length > 1) {
const index = Array.prototype.indexOf.call($el.parentElement.children, $el) + 1;
selector += ` > ${$el.tagName.toLowerCase()}:nth-child(${index})`;
}
else {
selector += ` > ${$el.tagName.toLowerCase()}`;
}
return selector;
}
}
new ElementHandler();
class DOMUtils extends ElementHandler {
constructor(option) {
super(option);
}
/** 版本号 */
version = version;
/**
* 取消挂载在window下的DOMUtils并返回DOMUtils
* @example
* let DOMUtils = window.DOMUtils.noConflict()
*/
noConflict() {
const that = this;
if (that.windowApi.window.DOMUtils) {
CommonUtils.delete(window, "DOMUtils");
}
that.windowApi.window.DOMUtils = this;
return this;
}
attr($el, attrName, attrValue) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
if (attrValue == null) {
// 获取属性
return that.attr($el[0], attrName, attrValue);
}
else {
// 设置属性
$el.forEach(($elItem) => {
that.attr($elItem, attrName, attrValue);
});
return;
}
}
if (attrValue == null) {
return $el.getAttribute(attrName);
}
else {
$el.setAttribute(attrName, attrValue);
}
}
createElement(
/** 元素名 */
tagName,
/** 属性 */
property,
/** 自定义属性 */
attributes) {
const that = this;
const $el = that.windowApi.document.createElement(tagName);
if (typeof property === "string") {
that.html($el, property);
return $el;
}
if (property == null) {
property = {};
}
if (attributes == null) {
attributes = {};
}
Object.keys(property).forEach((key) => {
const value = property[key];
if (key === "innerHTML") {
that.html($el, value);
return;
}
$el[key] = value;
});
Object.keys(attributes).forEach((key) => {
let value = attributes[key];
if (typeof value === "object") {
/* object转字符串 */
value = JSON.stringify(value);
}
else if (typeof value === "function") {
/* function转字符串 */
value = value.toString();
}
$el.setAttribute(key, value);
});
return $el;
}
css($el, property, value) {
const that = this;
/**
* 把纯数字没有px的加上
*/
function handlePixe(propertyName, propertyValue) {
const 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 $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
if (typeof property === "string") {
if (value == null) {
// 获取属性
return that.css($el[0], property);
}
else {
// 设置属性
$el.forEach(($elItem) => {
that.css($elItem, property);
});
return;
}
}
else if (typeof property === "object") {
// 设置属性
$el.forEach(($elItem) => {
that.css($elItem, property);
});
return;
}
return;
}
const setStyleProperty = (propertyName, propertyValue) => {
if (typeof propertyValue === "string" && propertyValue.trim().endsWith("!important")) {
propertyValue = propertyValue
.trim()
.replace(/!important$/gi, "")
.trim();
$el.style.setProperty(propertyName, propertyValue, "important");
}
else {
propertyValue = handlePixe(propertyName, propertyValue);
$el.style.setProperty(propertyName, propertyValue);
}
};
if (typeof property === "string") {
if (value == null) {
return that.windowApi.globalThis.getComputedStyle($el).getPropertyValue(property);
}
else {
setStyleProperty(property, value);
}
}
else if (typeof property === "object") {
for (const prop in property) {
const value = property[prop];
setStyleProperty(prop, value);
}
}
else {
// 其他情况
throw new TypeError("property must be string or object");
}
}
text($el, text) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
if (text == null) {
// 获取
return that.text($el[0]);
}
else {
// 设置
$el.forEach(($elItem) => {
that.text($elItem, text);
});
}
return;
}
if (text == null) {
return $el.textContent || $el.innerText;
}
else {
if (text instanceof Node) {
text = text.textContent || text.innerText;
}
if ("textContent" in $el) {
$el.textContent = text;
}
else if ("innerText" in $el) {
$el.innerText = text;
}
}
}
html($el, html) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
if (html == null) {
// 获取
return that.html($el[0]);
}
else {
// 设置
$el.forEach(($elItem) => {
that.html($elItem, html);
});
}
return;
}
if (html == null) {
// 获取
return $el.innerHTML;
}
else {
// 设置
if (html instanceof Element) {
html = html.innerHTML;
}
if ("innerHTML" in $el) {
CommonUtils.setSafeHTML($el, html);
}
}
}
/**
* 获取移动元素的transform偏移
*/
getTransform($el, isShow = false) {
const that = this;
let transform_left = 0;
let transform_top = 0;
if (!(isShow || (!isShow && CommonUtils.isShow($el)))) {
/* 未显示 */
const { recovery } = CommonUtils.forceShow($el);
const transformInfo = that.getTransform($el, true);
recovery();
return transformInfo;
}
const elementTransform = that.windowApi.globalThis.getComputedStyle($el).transform;
if (elementTransform != null && elementTransform !== "none" && elementTransform !== "") {
const elementTransformSplit = elementTransform.match(/\((.+)\)/)?.[1].split(",");
if (elementTransformSplit) {
transform_left = Math.abs(parseInt(elementTransformSplit[4]));
transform_top = Math.abs(parseInt(elementTransformSplit[5]));
}
else {
transform_left = 0;
transform_top = 0;
}
}
return {
transformLeft: transform_left,
transformTop: transform_top,
};
}
val($el, value) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
if (value == null) {
// 获取
return that.val($el[0]);
}
else {
// 设置
$el.forEach(($elItem) => {
that.val($elItem, value);
});
}
return;
}
if (value == null) {
// 获取
if ($el.localName === "input" && ($el.type === "checkbox" || $el.type === "radio")) {
return $el.checked;
}
else {
return $el.value;
}
}
else {
// 设置
if ($el.localName === "input" && ($el.type === "checkbox" || $el.type === "radio")) {
$el.checked = !!value;
}
else {
$el.value = value;
}
}
}
prop($el, propName, propValue) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
if (propValue == null) {
// 获取
return that.prop($el[0], propName);
}
else {
// 设置
$el.forEach(($elItem) => {
that.prop($elItem, propName, propValue);
});
}
return;
}
if (propValue == null) {
return Reflect.get($el, propName);
}
else {
if ($el instanceof Element && propName === "innerHTML") {
that.html($el, propValue);
}
else {
Reflect.set($el, propName, propValue);
}
}
}
/**
* 移除元素的属性
* @param $el 目标元素
* @param attrName 属性名
* @example
* // 移除元素a.xx的属性data-value
* DOMUtils.removeAttr(document.querySelector("a.xx"),"data-value")
* DOMUtils.removeAttr("a.xx","data-value")
* */
removeAttr($el, attrName) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.removeAttr($elItem, attrName);
});
return;
}
$el.removeAttribute(attrName);
}
/**
* 移除元素class名
* @param $el 目标元素
* @param className 类名
* @example
* // 移除元素a.xx的className为xx
* DOMUtils.removeClass(document.querySelector("a.xx"),"xx")
* DOMUtils.removeClass("a.xx","xx")
*/
removeClass($el, className) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.removeClass($elItem, className);
});
return;
}
if (className == null) {
// 清空全部className
$el.className = "";
}
else {
if (!Array.isArray(className)) {
className = className.trim().split(" ");
}
className.forEach((itemClassName) => {
$el.classList.remove(itemClassName);
});
}
}
/**
* 移除元素的属性
* @param $el 目标元素
* @param propName 属性名
* @example
* // 移除元素a.xx的href属性
* DOMUtils.removeProp(document.querySelector("a.xx"),"href")
* DOMUtils.removeProp("a.xx","href")
* */
removeProp($el, propName) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.removeProp($elItem, propName);
});
return;
}
CommonUtils.delete($el, propName);
}
/**
* 给元素添加class
* @param $el 目标元素
* @param className class名
* @example
* // 元素a.xx的className添加_vue_
* DOMUtils.addClass(document.querySelector("a.xx"),"_vue_")
* DOMUtils.addClass("a.xx","_vue_")
* */
addClass($el, className) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.addClass($elItem, className);
});
return;
}
if (!Array.isArray(className)) {
className = className.split(" ");
}
className.forEach((itemClassName) => {
if (itemClassName.trim() == "") {
return;
}
$el.classList.add(itemClassName);
});
}
/**
* 判断元素是否存在className
* @param $el
* @param className
*/
hasClass($el, className) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return false;
}
if (CommonUtils.isNodeList($el)) {
let flag = true;
for (let index = 0; index < $el.length; index++) {
const $elItem = $el[index];
flag = flag && that.hasClass($elItem, className);
}
return flag;
}
if (!$el?.classList) {
return false;
}
if (!Array.isArray(className)) {
className = className.split(" ");
}
for (let index = 0; index < className.length; index++) {
const item = className[index].trim();
if (!$el.classList.contains(item)) {
return false;
}
}
return true;
}
/**
* 函数在元素内部末尾添加子元素或HTML字符串
* @param $el 目标元素
* @param content 子元素或HTML字符串
* @example
* // 元素a.xx的内部末尾添加一个元素
* DOMUtils.append(document.querySelector("a.xx"),document.querySelector("b.xx"))
* DOMUtils.append("a.xx","'<b class="xx"></b>")
* */
append($el, content) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.append($elItem, content);
});
return;
}
function elementAppendChild(ele, text) {
if (typeof content === "string") {
if (ele instanceof DocumentFragment) {
if (typeof text === "string") {
text = that.toElement(text, true, false);
}
ele.appendChild(text);
}
else {
ele.insertAdjacentHTML("beforeend", CommonUtils.createSafeHTML(text));
}
}
else {
ele.appendChild(text);
}
}
if (Array.isArray(content) || content instanceof NodeList) {
/* 数组 */
const fragment = that.windowApi.document.createDocumentFragment();
content.forEach((ele) => {
if (typeof ele === "string") {
// 转为元素
ele = that.toElement(ele, true, false);
}
fragment.appendChild(ele);
});
$el.appendChild(fragment);
}
else {
elementAppendChild($el, content);
}
}
/**
* 函数 在元素内部开头添加子元素或HTML字符串
* @param $el 目标元素
* @param content 子元素或HTML字符串
* @example
* // 元素a.xx内部开头添加一个元素
* DOMUtils.prepend(document.querySelector("a.xx"),document.querySelector("b.xx"))
* DOMUtils.prepend("a.xx","'<b class="xx"></b>")
* */
prepend($el, content) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.prepend($elItem, content);
});
return;
}
if (typeof content === "string") {
if ($el instanceof DocumentFragment) {
content = that.toElement(content, true, false);
$el.prepend(content);
}
else {
$el.insertAdjacentHTML("afterbegin", CommonUtils.createSafeHTML(content));
}
}
else {
const $firstChild = $el.firstChild;
if ($firstChild == null) {
$el.prepend(content);
}
else {
$el.insertBefore(content, $firstChild);
}
}
}
/**
* 在元素后面添加兄弟元素或HTML字符串
* @param $el 目标元素
* @param content 兄弟元素或HTML字符串
* @example
* // 元素a.xx后面添加一个元素
* DOMUtils.after(document.querySelector("a.xx"),document.querySelector("b.xx"))
* DOMUtils.after("a.xx","'<b class="xx"></b>")
* */
after($el, content) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.after($elItem, content);
});
return;
}
if (typeof content === "string") {
$el.insertAdjacentHTML("afterend", CommonUtils.createSafeHTML(content));
}
else {
const $parent = $el.parentElement;
const $nextSlibling = $el.nextSibling;
if (!$parent || $nextSlibling) {
// 任意一个不行
$el.after(content);
}
else {
$parent.insertBefore(content, $nextSlibling);
}
}
}
/**
* 在元素前面添加兄弟元素或HTML字符串
* @param $el 目标元素
* @param content 兄弟元素或HTML字符串
* @example
* // 元素a.xx前面添加一个元素
* DOMUtils.before(document.querySelector("a.xx"),document.querySelector("b.xx"))
* DOMUtils.before("a.xx","'<b class="xx"></b>")
* */
before($el, content) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.before($elItem, content);
});
return;
}
if (typeof content === "string") {
$el.insertAdjacentHTML("beforebegin", CommonUtils.createSafeHTML(content));
}
else {
const $parent = $el.parentElement;
if (!$parent) {
$el.before(content);
}
else {
$parent.insertBefore(content, $el);
}
}
}
/**
* 移除元素
* @param $el 目标元素
* @example
* // 元素a.xx前面添加一个元素
* DOMUtils.remove(document.querySelector("a.xx"))
* DOMUtils.remove(document.querySelectorAll("a.xx"))
* DOMUtils.remove("a.xx")
* */
remove($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
$el.forEach(($elItem) => {
that.remove($elItem);
});
return;
}
if (typeof $el.remove === "function") {
$el.remove();
}
else if ($el.parentElement) {
$el.parentElement.removeChild($el);
}
else if ($el.parentNode) {
$el.parentNode.removeChild($el);
}
}
/**
* 移除元素的所有子元素
* @param $el 目标元素
* @example
* // 移除元素a.xx元素的所有子元素
* DOMUtils.empty(document.querySelector("a.xx"))
* DOMUtils.empty("a.xx")
* */
empty($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.empty($elItem);
});
return;
}
if ($el.innerHTML) {
$el.innerHTML = "";
}
else if ($el.textContent) {
$el.textContent = "";
}
}
/**
* 获取元素相对于文档的偏移坐标(加上文档的滚动条)
* @param $el 目标元素
* @example
* // 获取元素a.xx的对于文档的偏移坐标
* DOMUtils.offset(document.querySelector("a.xx"))
* DOMUtils.offset("a.xx")
* > 0
*/
offset($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selector($el);
}
if ($el == null) {
return;
}
const rect = $el.getBoundingClientRect();
return {
/** y轴偏移 */
top: rect.top + that.windowApi.globalThis.scrollY,
/** x轴偏移 */
left: rect.left + that.windowApi.globalThis.scrollX,
};
}
width($el, isShow = false) {
const that = this;
if (typeof $el === "string") {
$el = that.selector($el);
}
if (CommonUtils.isWin($el)) {
return that.windowApi.window.document.documentElement.clientWidth;
}
if ($el.nodeType === 9) {
/* Document文档节点 */
$el = $el;
return Math.max($el.body.scrollWidth, $el.documentElement.scrollWidth, $el.body.offsetWidth, $el.documentElement.offsetWidth, $el.documentElement.clientWidth);
}
if (isShow || (!isShow && CommonUtils.isShow($el))) {
/* 已显示 */
/* 不从style中获取对应的宽度,因为可能使用了class定义了width !important */
$el = $el;
/* 如果element.style.width为空 则从css里面获取是否定义了width信息如果定义了 则读取css里面定义的宽度width */
if (parseFloat(CommonUtils.getStyleValue($el, "width").toString()) > 0) {
return parseFloat(CommonUtils.getStyleValue($el, "width").toString());
}
/* 如果从css里获取到的值不是大于0 可能是auto 则通过offsetWidth来进行计算 */
if ($el.offsetWidth > 0) {
const borderLeftWidth = CommonUtils.getStyleValue($el, "borderLeftWidth");
const borderRightWidth = CommonUtils.getStyleValue($el, "borderRightWidth");
const paddingLeft = CommonUtils.getStyleValue($el, "paddingLeft");
const paddingRight = CommonUtils.getStyleValue($el, "paddingRight");
const backHeight = parseFloat($el.offsetWidth.toString()) -
parseFloat(borderLeftWidth.toString()) -
parseFloat(borderRightWidth.toString()) -
parseFloat(paddingLeft.toString()) -
parseFloat(paddingRight.toString());
return parseFloat(backHeight.toString());
}
return 0;
}
else {
/* 未显示 */
$el = $el;
const { recovery } = CommonUtils.forceShow($el);
const width = that.width($el, true);
recovery();
return width;
}
}
height($el, isShow = false) {
const that = this;
if (CommonUtils.isWin($el)) {
return that.windowApi.window.document.documentElement.clientHeight;
}
if (typeof $el === "string") {
$el = that.selector($el);
}
if ($el.nodeType === 9) {
$el = $el;
/* Document文档节点 */
return Math.max($el.body.scrollHeight, $el.documentElement.scrollHeight, $el.body.offsetHeight, $el.documentElement.offsetHeight, $el.documentElement.clientHeight);
}
if (isShow || (!isShow && CommonUtils.isShow($el))) {
$el = $el;
/* 已显示 */
/* 从style中获取对应的高度,因为可能使用了class定义了width !important */
/* 如果element.style.height为空 则从css里面获取是否定义了height信息如果定义了 则读取css里面定义的高度height */
if (parseFloat(CommonUtils.getStyleValue($el, "height").toString()) > 0) {
return parseFloat(CommonUtils.getStyleValue($el, "height").toString());
}
/* 如果从css里获取到的值不是大于0 可能是auto 则通过offsetHeight来进行计算 */
if ($el.offsetHeight > 0) {
const borderTopWidth = CommonUtils.getStyleValue($el, "borderTopWidth");
const borderBottomWidth = CommonUtils.getStyleValue($el, "borderBottomWidth");
const paddingTop = CommonUtils.getStyleValue($el, "paddingTop");
const paddingBottom = CommonUtils.getStyleValue($el, "paddingBottom");
const backHeight = parseFloat($el.offsetHeight.toString()) -
parseFloat(borderTopWidth.toString()) -
parseFloat(borderBottomWidth.toString()) -
parseFloat(paddingTop.toString()) -
parseFloat(paddingBottom.toString());
return parseFloat(backHeight.toString());
}
return 0;
}
else {
/* 未显示 */
$el = $el;
const { recovery } = CommonUtils.forceShow($el);
const height = that.height($el, true);
recovery();
return height;
}
}
outerWidth($el, isShow = false) {
const that = this;
if (CommonUtils.isWin($el)) {
return that.windowApi.window.innerWidth;
}
if (typeof $el === "string") {
$el = that.selector($el);
}
$el = $el;
if (isShow || (!isShow && CommonUtils.isShow($el))) {
const style = that.windowApi.globalThis.getComputedStyle($el, null);
const marginLeft = CommonUtils.getStyleValue(style, "marginLeft");
const marginRight = CommonUtils.getStyleValue(style, "marginRight");
return $el.offsetWidth + marginLeft + marginRight;
}
else {
const { recovery } = CommonUtils.forceShow($el);
const outerWidth = that.outerWidth($el, true);
recovery();
return outerWidth;
}
}
outerHeight($el, isShow = false) {
const that = this;
if (CommonUtils.isWin($el)) {
return that.windowApi.window.innerHeight;
}
if (typeof $el === "string") {
$el = that.selector($el);
}
$el = $el;
if (isShow || (!isShow && CommonUtils.isShow($el))) {
const style = that.windowApi.globalThis.getComputedStyle($el, null);
const marginTop = CommonUtils.getStyleValue(style, "marginTop");
const marginBottom = CommonUtils.getStyleValue(style, "marginBottom");
return $el.offsetHeight + marginTop + marginBottom;
}
else {
const { recovery } = CommonUtils.forceShow($el);
const outerHeight = that.outerHeight($el, true);
recovery();
return outerHeight;
}
}
/**
* 将一个元素替换为另一个元素
* @param $el 目标元素
* @param $newEl 新元素
* @example
* // 替换元素a.xx为b.xx
* DOMUtils.replaceWith(document.querySelector("a.xx"),document.querySelector("b.xx"))
* DOMUtils.replaceWith("a.xx",'<b class="xx"></b>')
*/
replaceWith($el, $newEl) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.replaceWith($elItem, $newEl);
});
return;
}
if (typeof $newEl === "string") {
$newEl = that.toElement($newEl, false, false);
}
const $parent = $el.parentElement;
if ($parent) {
$parent.replaceChild($newEl, $el);
}
else {
that.after($el, $newEl);
$el.remove();
}
}
/**
* 将一个元素包裹在指定的HTML元素中
* @param $el 要包裹的元素
* @param wrapperHTML 要包裹的HTML元素的字符串表示形式
* @example
* // 将a.xx元素外面包裹一层div
* DOMUtils.wrap(document.querySelector("a.xx"),"<div></div>")
*/
wrap($el, wrapperHTML) {
const that = this;
if (typeof $el === "string") {
$el = that.selectorAll($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
// 设置
$el.forEach(($elItem) => {
that.wrap($elItem, wrapperHTML);
});
return;
}
$el = $el;
// 创建一个新的div元素,并将wrapperHTML作为其innerHTML
const $wrapper = that.windowApi.document.createElement("div");
that.html($wrapper, wrapperHTML);
const wrapperFirstChild = $wrapper.firstChild;
// 将要包裹的元素插入目标元素前面
const parentElement = $el.parentElement;
parentElement.insertBefore(wrapperFirstChild, $el);
// 将要包裹的元素移动到wrapper中
wrapperFirstChild.appendChild($el);
}
prev($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selector($el);
}
if ($el == null) {
return;
}
return $el.previousElementSibling;
}
next($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selector($el);
}
if ($el == null) {
return;
}
return $el.nextElementSibling;
}
siblings($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selector($el);
}
if ($el == null) {
return;
}
return Array.from($el.parentElement.children).filter(($child) => $child !== $el);
}
/**
* 获取当前元素的父元素
* @param $el 当前元素
* @returns 父元素
* @example
* // 获取a.xx元素的父元素
* DOMUtils.parent(document.querySelector("a.xx"))
* DOMUtils.parent("a.xx")
* > <div ...>....</div>
*/
parent($el) {
const that = this;
if (typeof $el === "string") {
$el = that.selector($el);
}
if ($el == null) {
return;
}
if (CommonUtils.isNodeList($el)) {
const resultArray = [];
$el.forEach(($elItem) => {
resultArray.push(that.parent($elItem));
});
return resultArray;
}
else {
return $el.parentElement;
}
}
toElement(html, useParser = false, isComplete = false) {
const that = this;
// 去除html前后的空格
html = html.trim();
function parseHTMLByDOMParser() {
const parser = new DOMParser();
if (isComplete) {
return parser.parseFromString(html, "text/html");
}
else {
return parser.parseFromString(html, "text/html").body.firstChild;
}
}
function parseHTMLByCreateDom() {
const $el = that.windowApi.document.createElement("div");
that.html($el, html);
if (isComplete) {
return $el;
}
else {
return $el.firstElementChild ?? $el.firstChild;
}
}
if (useParser) {
return parseHTMLByDOMParser();
}
else {
return parseHTMLByCreateDom();
}
}
/**
* 序列化表单元素
* @param $form 表单元素
* @example
* DOMUtils.serialize(document.querySelector("form"))
* > xxx=xxx&aaa=
*/
serialize($form) {
const elements = $form.elements;
const serializedArray = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (element.name &&
!element.disabled &&
(element.checked ||
["text", "hidden", "password", "textarea", "select-one", "select-multiple"].includes(element.type))) {
if (element.type === "select-multiple") {
for (let j = 0; j < element.options.length; j++) {
if (element.options[j].selected) {
serializedArray.push({
name: element.name,
value: element.options[j].value,
});
}
}
}
else {
serializedArray.push({ name: element.name, value: element.value });
}
}
}
return serializedArray
.map((item) => `${encodeURIComponent(item.name)}=${encodeURIComponent(item.value)}`)
.join("&");
}
/**
* 创建一个新的DOMUtils实例
* @param option
* @returns
*/
createDOMUtils(option) {
return new DOMUtils(option);
}
/**
* 获取文字的位置信息
* @param $input 输入框
* @param selectionStart 起始位置
* @param selectionEnd 结束位置
* @example
* DOMUtils.getTextBoundingRect(document.querySelector("input"));
*/
getTextBoundingRect($input, selectionStart, selectionEnd) {
const that = this;
// Basic parameter validation
if (!$input || !("value" in $input))
return $input;
if (selectionStart == null) {
selectionStart = $input.selectionStart || 0;
}
if (selectionEnd == null) {
selectionEnd = $input.selectionEnd || 0;
}
if (typeof selectionStart == "string")
selectionStart = parseFloat(selectionStart);
if (typeof selectionStart != "number" || isNaN(selectionStart)) {
selectionStart = 0;
}
if (selectionStart < 0)
selectionStart = 0;
else
selectionStart = Math.min($input.value.length, selectionStart);
if (typeof selectionEnd == "string")
selectionEnd = parseFloat(selectionEnd);
if (typeof selectionEnd != "number" || isNaN(selectionEnd) || selectionEnd < selectionStart) {
selectionEnd = selectionStart;
}
if (selectionEnd < 0)
selectionEnd = 0;
else
selectionEnd = Math.min($input.value.length, selectionEnd);
// If available (thus IE), use the createTextRange method
if (typeof $input.createTextRange == "function") {
const range = $input.createTextRange();
range.collapse(true);
range.moveStart("character", selectionStart);
range.moveEnd("character", selectionEnd - selectionStart);
return range.getBoundingClientRect();
}
// createTextRange is not supported, create a fake text range
const offset = getInputOffset(), width = getInputCSS("width", true), height = getInputCSS("height", true);
let topPos = offset.top;
let leftPos = offset.left;
// Styles to simulate a node in an input field
let cssDefaultStyles = "white-space:pre;padding:0;margin:0;";
const listOfModifiers = [
"direction",
"font-family",
"font-size",
"font-size-adjust",
"font-variant",
"font-weight",
"font-style",
"letter-spacing",
"line-height",
"text-align",
"text-indent",
"text-transform",
"word-wrap",
"word-spacing",
];
topPos += getInputCSS("padding-top", true);
topPos += getInputCSS("border-top-width", true);
leftPos += getInputCSS("padding-left", true);
leftPos += getInputCSS("border-left-width", true);
leftPos += 1; //Seems to be necessary
for (let index = 0; index < listOfModifiers.length; index++) {
const property = listOfModifiers[index];
cssDefaultStyles += property + ":" + getInputCSS(property, false) + ";";
}
// End of CSS variable checks
// 不能为空,不然获取不到高度
const text = $input.value || "G", textLen = text.length, fakeClone = that.windowApi.document.createElement("div");
if (selectionStart > 0)
appendPart(0, selectionStart);
const fakeRange = appendPart(selectionStart, selectionEnd);
if (textLen > selectionEnd)
appendPart(selectionEnd, textLen);
// Styles to inherit the font styles of the element
fakeClone.style.cssText = cssDefaultStyles;
// Styles to position the text node at the desired position
fakeClone.style.position = "absolute";
fakeClone.style.top = topPos + "px";
fakeClone.style.left = leftPos + "px";
fakeClone.style.width = width + "px";
fakeClone.style.height = height + "px";
that.windowApi.document.body.appendChild(fakeClone);
const returnValue = fakeRange.getBoundingClientRect(); //Get rect
fakeClone?.parentNode?.removeChild(fakeClone); //Remove temp
return returnValue;
// Local functions for readability of the previous code
/**
*
* @param start
* @param end
* @returns
*/
function appendPart(start, end) {
const span = that.windowApi.document.createElement("span");
span.style.cssText = cssDefaultStyles; //Force styles to prevent unexpected results
span.textContent = text.substring(start, end);
fakeClone.appendChild(span);
return span;
}
// Computing offset position
function getInputOffset() {
const body = that.windowApi.document.body, win = that.windowApi.document.defaultView, docElem = that.windowApi.document.documentElement, $box = that.windowApi.document.createElement("div");
$box.style.paddingLeft = $box.style.width = "1px";
body.appendChild($box);
const isBoxModel = $box.offsetWidth == 2;
body.removeChild($box);
const $boxRect = $input.getBoundingClientRect();
const clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, scrollTop = win.pageYOffset || (isBoxModel && docElem.scrollTop) || body.scrollTop, scrollLeft = win.pageXOffset || (isBoxModel && docElem.scrollLeft) || body.scrollLeft;
return {
top: $boxRect.top + scrollTop - clientTop,
left: $boxRect.left + scrollLeft - clientLeft,
};
}
/**
*
* @param prop
* @param isNumber
* @returns
*/
function getInputCSS(prop, isNumber) {
const val = that.windowApi.document.defaultView.getComputedStyle($input, null).getPropertyValue(prop);
if (isNumber) {
return parseFloat(val);
}
else {
return val;
}
}
}
addStyle(cssText) {
if (typeof cssText !== "string") {
throw new Error("DOMUtils.addStyle 参数cssText 必须为String类型");
}
const $css = this.createElement("style", {
type: "text/css",
innerHTML: cssText,
});
if (this.windowApi.document.head) {
/* 插入head最后 */
this.windowApi.document.head.appendChild($css);
}
else if (this.windowApi.document.documentElement.childNodes.length === 0) {
/* 插入#html后 */
this.windowApi.document.documentElement.appendChild($css);
}
else {
/* 插入#html第一个元素前 */
this.windowApi.document.documentElement.insertBefore($css, this.windowApi.document.documentElement.childNodes[0]);
}
return $css;
}
/**
* 检测点击的地方是否在该元素区域内
* @param $el 需要检测的元素
* @returns
* + true 点击在元素上
* + false 未点击在元素上
* @example
* DOMUtils.checkUserClickInNode(document.querySelector(".xxx"));
* > false
**/
checkUserClickInNode($el) {
const that = this;
if (!CommonUtils.isDOM($el)) {
throw new Error("Utils.checkUserClickInNode 参数 targetNode 必须为 Element|Node 类型");
}
const clickEvent = that.windowApi.window.event;
const touchEvent = that.windowApi.window.event;
const $click = clickEvent?.composedPath()?.[0];
// 点击的x坐标
const clickPosX = clickEvent?.clientX != null ? clickEvent.clientX : touchEvent.touches[0].clientX;
// 点击的y坐标
const clickPosY = clickEvent?.clientY != null ? clickEvent.clientY : touchEvent.touches[0].clientY;
const {
/* 要检测的元素的相对屏幕的横坐标最左边 */
left: elementPosXLeft,
/* 要检测的元素的相对屏幕的横坐标最右边 */
right: elementPosXRight,
/* 要检测的元素的相对屏幕的纵坐标最上边 */
top: elementPosYTop,
/* 要检测的元素的相对屏幕的纵坐标最下边 */
bottom: elementPosYBottom, } = $el.getBoundingClientRect();
if (clickPosX >= elementPosXLeft &&
clickPosX <= elementPosXRight &&
clickPosY >= elementPosYTop &&
clickPosY <= elementPosYBottom) {
return true;
}
else if (($click && $el.contains($click)) || $click == $el) {
/* 这种情况是应对在界面中隐藏的元素,getBoundingClientRect获取的都是0 */
return true;
}
else {
return false;
}
}
deleteParentNode($el, parentSelector) {
if ($el == null) {
return;
}
if (!CommonUtils.isDOM($el)) {
throw new Error("DOMUtils.deleteParentNode 参数 target 必须为 Node|HTMLElement 类型");
}
if (typeof parentSelector !== "string") {
throw new Error("DOMUtils.deleteParentNode 参数 targetSelector 必须为 string 类型");
}
let result = false;
const $parent = domUtils.closest($el, parentSelector);
if ($parent) {
this.remove($parent);
result = true;
}
return result;
}
*findElementsWithText($el, text, filter) {
const that = this;
if ($el.outerHTML.includes(text)) {
if ($el.children.length === 0) {
const filterResult = typeof filter === "function" ? filter($el) : false;
if (!filterResult) {
yield $el;
}
}
else {
const $text = Array.from($el.childNodes).filter(($child) => $child.nodeType === Node.TEXT_NODE);
for (const $child of $text) {
if ($child.textContent.includes(text)) {
const filterResult = typeof filter === "function" ? filter($el) : false;
if (!filterResult) {
yield $child;
}
}
}
}
}
for (let index = 0; index < $el.children.length; index++) {
const $child = $el.children[index];
yield* that.findElementsWithText($child, text, filter);
}
}
/**
* 寻找可见元素,如果元素不可见,则向上找它的父元素直至找到,如果父元素不存在则返回null
* @param $el
* @example
* let visibleElement = DOMUtils.findVisibleElement(document.querySelector("a.xx"));
* > <HTMLElement>
*/
findVisibleElement($el) {
let $current = $el;
while ($current) {
const rect = $current.getBoundingClientRect();
if (rect.length) {
return $current;
}
$current = $current.parentElement;
}
return null;
}
/**
* 将元素上的文本或元素使用光标进行选中
*
* 注意,如果设置startIndex和endIndex,且元素上并无可选则的坐标,那么会报错
* @param $el 目标元素
* @param childTextNode 目标元素下的#text元素
* @param startIndex (可选)开始坐标,可为空
* @param endIndex (可选)结束坐标,可为空
* @example
* DOMUtils.setElementSelection(document.querySelector("span"));
*/
setElementSelection($el, childTextNode, startIndex, endIndex) {
const range = this.windowApi.document.createRange();
range.selectNodeContents($el);
if (childTextNode) {
if (childTextNode.nodeType !== Node.TEXT_NODE) {
throw new TypeError("childTextNode必须是#text元素");
}
if (startIndex != null && endIndex != null) {
range.setStart(childTextNode, startIndex);
range.setEnd(childTextNode, endIndex);
}
}
const selection = this.windowApi.globalThis.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
}
}
const domUtils = new DOMUtils();
return domUtils;
}));
//# sourceMappingURL=index.umd.js.map