您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
使用js重新对jQuery的部分函数进行了仿写
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/465772/1668404/DOMUtils.js
(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