import { PopsCommonCSSClassName } from "../config/CommonCSSClassName"; import { OriginPrototype, PopsCore } from "../PopsCore"; import type { ParseHTMLReturnType, PopsDOMUtils_Event, PopsDOMUtils_EventType, PopsDOMUtilsAddEventListenerResult, PopsDOMUtilsCreateElementAttributesMap, PopsDOMUtilsCSSProperty, PopsDOMUtilsCSSPropertyType, PopsDOMUtilsElementEventType, PopsDOMUtilsEventListenerOption, PopsDOMUtilsEventListenerOptionsAttribute, PopsDOMUtilsTargetElementType, } from "../types/PopsDOMUtilsEventType"; import { PopsSafeUtils } from "./PopsSafeUtils"; import { popsUtils } from "./PopsUtils"; /* 数据 */ const GlobalData = { /** .on添加在元素存储的事件 */ domEventSymbol: Symbol("events_" + (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)), }; const CommonUtils = { isWin: popsUtils.isWin.bind(popsUtils), delete: popsUtils.delete.bind(popsUtils), isNodeList: popsUtils.isNodeList.bind(popsUtils), }; class PopsDOMUtilsEvent { get windowApi() { return PopsCore.window; } /** * 绑定事件 * @param element 需要绑定的元素|元素数组|window * @param eventType 需要监听的事件 * @param handler 绑定事件触发的回调函数 * @param option * + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发 * + once 表示事件是否只触发一次。默认为false * + passive 表示事件监听器是否不会调用preventDefault()。默认为false * @example * // 监听元素a.xx的click事件 * DOMUtils.on(document.querySelector("a.xx"),"click",(event)=>{ * console.log("事件触发",event) * }) * DOMUtils.on("a.xx","click",(event)=>{ * console.log("事件触发",event) * }) */ on( element: PopsDOMUtilsElementEventType, eventType: T | T[], handler: (this: E, event: PopsDOMUtils_Event[T]) => void, option?: PopsDOMUtilsEventListenerOption | boolean ): PopsDOMUtilsAddEventListenerResult; /** * 绑定事件 * @param element 需要绑定的元素|元素数组|window * @param eventType 需要监听的事件 * @param handler 绑定事件触发的回调函数 * @param option * + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发 * + once 表示事件是否只触发一次。默认为false * + passive 表示事件监听器是否不会调用preventDefault()。默认为false * @example * // 监听元素a.xx的click事件 * DOMUtils.on(document.querySelector("a.xx"),"click",(event)=>{ * console.log("事件触发",event) * }) * DOMUtils.on("a.xx","click",(event)=>{ * console.log("事件触发",event) * }) */ on( element: PopsDOMUtilsElementEventType, eventType: string | string[], handler: (this: E, event: T) => void, option?: PopsDOMUtilsEventListenerOption | boolean ): PopsDOMUtilsAddEventListenerResult; /** * 绑定事件 * @param element 需要绑定的元素|元素数组|window * @param eventType 需要监听的事件 * @param selector 子元素选择器 * @param handler 绑定事件触发的回调函数 * @param option * + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发 * + once 表示事件是否只触发一次。默认为false * + passive 表示事件监听器是否不会调用preventDefault()。默认为false * @example * // 监听元素a.xx的click、tap、hover事件 * DOMUtils.on(document.querySelector("a.xx"),"click tap hover",(event, $selector)=>{ * console.log("事件触发", event, $selector) * }) * DOMUtils.on("a.xx",["click","tap","hover"],(event, $selector)=>{ * console.log("事件触发", event, $selector) * }) * @example * // 监听全局document下的子元素a.xx的click事件 * DOMUtils.on(document,"click tap hover","a.xx",(event, $selector)=>{ * console.log("事件触发", event, $selector) * }) */ on( element: PopsDOMUtilsElementEventType, eventType: T | T[], selector: string | string[] | undefined | null, handler: (this: E, event: PopsDOMUtils_Event[T], $selector: E) => void, option?: PopsDOMUtilsEventListenerOption | boolean ): PopsDOMUtilsAddEventListenerResult; /** * 绑定事件 * @param element 需要绑定的元素|元素数组|window * @param eventType 需要监听的事件 * @param selector 子元素选择器 * @param handler 绑定事件触发的回调函数 * @param option * + capture 表示事件是否在捕获阶段触发。默认为false,即在冒泡阶段触发 * + once 表示事件是否只触发一次。默认为false * + passive 表示事件监听器是否不会调用preventDefault()。默认为false * @example * // 监听元素a.xx的click、tap、hover事件 * DOMUtils.on(document.querySelector("a.xx"),"click tap hover",(event, $selector)=>{ * console.log("事件触发", event, $selector) * }) * DOMUtils.on("a.xx",["click","tap","hover"],(event, $selector)=>{ * console.log("事件触发", event, $selector) * }) * @example * // 监听全局document下的子元素a.xx的click事件 * DOMUtils.on(document,"click tap hover","a.xx",(event, $selector)=>{ * console.log("事件触发", event, $selector) * }) */ on( element: PopsDOMUtilsElementEventType, eventType: string | string[], selector: string | string[] | undefined | null, handler: (this: E, event: T, $selector: E) => void, option?: PopsDOMUtilsEventListenerOption | boolean ): PopsDOMUtilsAddEventListenerResult; on( element: HTMLElement | string | NodeList | HTMLElement[] | Window | Document | Element | null | typeof globalThis, eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string | string[], selector: | string | string[] | undefined | ((this: E, event: T, $selector: E) => void) | null, callback?: | ((this: E, event: T, $selector: E) => void) | PopsDOMUtilsEventListenerOption | boolean, option?: PopsDOMUtilsEventListenerOption | boolean ): PopsDOMUtilsAddEventListenerResult { /** * 获取option配置 * @param args * @param startIndex * @param option */ const getOption = function (args: IArguments, startIndex: number, option: PopsDOMUtilsEventListenerOption) { const currentParam: boolean | PopsDOMUtilsEventListenerOption = 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 (currentParam && typeof currentParam === "object") { for (const key in option) { if (Reflect.has(currentParam, key)) { Reflect.set(option, key, currentParam[key as keyof typeof currentParam]); } } } 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 { off() {}, emit() {}, }; } let $elList: (Element | Document | Window)[] = []; if (element instanceof NodeList || Array.isArray(element)) { $elList = $elList.concat(Array.from(element as Element[])); } else { $elList.push(element as Element); } // 事件名 let eventTypeList: string[] = []; if (Array.isArray(eventType)) { eventTypeList = eventTypeList.concat(eventType.filter((it) => typeof it === "string" && it.toString() !== "")); } else if (typeof eventType === "string") { eventTypeList = eventTypeList.concat(eventType.split(" ").filter((it) => it !== "")); } // 子元素选择器 let selectorList: string[] = []; if (Array.isArray(selector)) { selectorList = selectorList.concat(selector.filter((it) => typeof it === "string" && it.toString() !== "")); } else if (typeof selector === "string") { selectorList.push(selector); } // 事件回调 let listenerCallBack: (this: Element, event: Event, $selector?: HTMLElement) => void | boolean = callback as ( this: Element, event: Event, $selector?: HTMLElement ) => void | boolean; // 事件配置 let listenerOption: PopsDOMUtilsEventListenerOption = { capture: false, once: false, passive: false, isComposedPath: false, overrideTarget: true, }; if (typeof selector === "function") { // 这是为没有selector的情况 // 那么它就是callback listenerCallBack = selector as any; listenerOption = getOption(args, 3, listenerOption); } else { // 这是存在selector的情况 listenerOption = getOption(args, 4, listenerOption); } $elList.forEach(($elItem) => { // window和document共用一个对象 // 这样就能处理子元素选择器无法匹配的问题 const targetIsWindow = CommonUtils.isWin($elItem); // 遍历事件名设置元素事件 eventTypeList.forEach((eventName) => { /** * 如果是option.once,那么删除该监听和元素上的事件和监听 */ const checkOptionOnceToRemoveEventListener = () => { if (listenerOption.once) { this.off($elItem, eventName, selector as any, callback as any, option); } }; /** * 事件回调 * @param event */ const handlerCallBack = function (event: Event) { if (listenerOption.isPreventEvent) { that.preventEvent(event); } let call_this: Element | undefined = void 0; let call_event: Event | undefined = void 0; let call_$selector: HTMLElement | undefined = void 0; let execCallback = false; if (selectorList.length) { // 存在子元素选择器 // 这时候的this和target都是子元素选择器的元素 let $target: HTMLElement; if (listenerOption.isComposedPath) { // 可能为空 const composedPath = event.composedPath(); if (!composedPath.length && event.target) { composedPath.push(event.target); } $target = composedPath[0] as HTMLElement; } else { $target = event.target as HTMLElement; } const $parent = targetIsWindow ? that.windowApi.document.documentElement : $elItem; const findValue = selectorList.find((selectors) => { // 判断目标元素是否匹配选择器 if (that.matches($target, selectors)) { // 当前目标可以被selector所匹配到 return true; } // 在上层与主元素之间寻找可以被selector所匹配到的 const $closestMatches = that.closest($target, selectors); if ($closestMatches && ($parent)?.contains?.($closestMatches)) { $target = $closestMatches; return true; } return false; }); if (findValue) { if (listenerOption.overrideTarget) { // 这里尝试使用defineProperty修改event的target值 try { const originTarget = event.target; OriginPrototype.Object.defineProperties(event, { target: { get() { return $target; }, }, originTarget: { get() { return originTarget; }, }, }); // oxlint-disable-next-line no-empty } catch {} } execCallback = true; call_this = $target; call_event = event; call_$selector = $target; } } else { execCallback = true; call_this = $elItem as Element; call_event = event; } if (execCallback) { const result = listenerCallBack.call(call_this!, call_event!, call_$selector!); checkOptionOnceToRemoveEventListener(); if (typeof result === "boolean" && !result) { return false; } } }; // add listener $elItem.addEventListener(eventName, handlerCallBack, listenerOption); // 获取对象上的事件 const elementEvents: { [k: string]: PopsDOMUtilsEventListenerOptionsAttribute[]; } = Reflect.get($elItem, GlobalData.domEventSymbol) || {}; // 初始化对象上的xx事件 elementEvents[eventName] = elementEvents[eventName] || []; elementEvents[eventName].push({ selector: selectorList, option: listenerOption, handlerCallBack: handlerCallBack, callback: listenerCallBack, }); // 覆盖事件 Reflect.set($elItem, GlobalData.domEventSymbol, elementEvents); }); }); return { /** * 取消绑定的监听事件 * @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件 */ off: ( filter?: ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean ) => { that.off($elList, eventTypeList, selectorList, listenerCallBack, listenerOption, filter); }, /** * 主动触发事件 * @param extraDetails 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象 * @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用callback,但是这种会让使用了`$selector`的没有值 */ emit: (extraDetails?: object, useDispatchToTriggerEvent?: boolean) => { that.emit($elList, eventTypeList, extraDetails, useDispatchToTriggerEvent); }, }; } /** * 取消绑定事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType 需要取消监听的事件 * @param callback 通过DOMUtils.on绑定的事件函数 * @param option * + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true * @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件 * @example * // 取消监听元素a.xx所有的click事件 * DOMUtils.off(document.querySelector("a.xx"),"click") * DOMUtils.off("a.xx","click") */ off( element: PopsDOMUtilsElementEventType, eventType: T | T[], callback?: (this: E, event: PopsDOMUtils_Event[T]) => void, option?: EventListenerOptions | boolean, filter?: ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean ): void; /** * 取消绑定事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType 需要取消监听的事件 * @param callback 通过DOMUtils.on绑定的事件函数 * @param option * + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true * @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件 * @example * // 取消监听元素a.xx的click事件 * DOMUtils.off(document.querySelector("a.xx"),"click") * DOMUtils.off("a.xx","click") */ off( element: PopsDOMUtilsElementEventType, eventType: string | string[], callback?: (this: E, event: T) => void, option?: EventListenerOptions | boolean, filter?: ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean ): void; /** * 取消绑定事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType 需要取消监听的事件 * @param selector 子元素选择器 * @param callback 通过DOMUtils.on绑定的事件函数 * @param option * + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true * @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件 * @example * // 取消监听元素a.xx的click、tap、hover事件 * DOMUtils.off(document.querySelector("a.xx"),"click tap hover") * DOMUtils.off("a.xx",["click","tap","hover"]) */ off( element: PopsDOMUtilsElementEventType, eventType: T | T[], selector?: string | string[] | undefined | null, callback?: (this: E, event: PopsDOMUtils_Event[T], $selector: E) => void, option?: EventListenerOptions | boolean, filter?: ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean ): void; /** * 取消绑定事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType 需要取消监听的事件 * @param selector 子元素选择器 * @param callback 通过DOMUtils.on绑定的事件函数 * @param option * + capture 如果在添加事件监听器时指定了useCapture为true,则在移除事件监听器时也必须指定为true * @param filter (可选)过滤函数,对元素属性上的事件进行过滤出想要删除的事件 * @example * // 取消监听元素a.xx的click、tap、hover事件 * DOMUtils.off(document.querySelector("a.xx"),"click tap hover") * DOMUtils.off("a.xx",["click","tap","hover"]) */ off( element: PopsDOMUtilsElementEventType, eventType: string | string[], selector?: string | string[] | undefined | null, callback?: (this: E, event: T, $selector: E) => void, option?: EventListenerOptions | boolean, filter?: ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean ): void; off( element: HTMLElement | string | NodeList | HTMLElement[] | Window | Document | Element | null | typeof globalThis, eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string | string[], selector: | string | string[] | undefined | ((this: E, event: T, $selector: E) => void) | null, callback?: | ((this: E, event: T, $selector: E) => void) | EventListenerOptions | boolean, option?: | EventListenerOptions | boolean | (( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean), filter?: ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean ) { /** * 获取option配置 * @param args1 * @param startIndex * @param option */ const getOption = function (args1: IArguments, startIndex: number, option: EventListenerOptions) { const currentParam: boolean | PopsDOMUtilsEventListenerOption = args1[startIndex]; if (typeof currentParam === "boolean") { option.capture = currentParam; } else if (currentParam && typeof currentParam === "object" && "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: HTMLElement[] = []; if (element instanceof NodeList || Array.isArray(element)) { element = element as HTMLElement[]; $elList = $elList.concat(Array.from(element)); } else { $elList.push(element as HTMLElement); } let eventTypeList: string[] = []; if (Array.isArray(eventType)) { eventTypeList = eventTypeList.concat(eventType.filter((it) => typeof it === "string" && it.toString() !== "")); } else if (typeof eventType === "string") { eventTypeList = eventTypeList.concat(eventType.split(" ").filter((it) => it !== "")); } // 子元素选择器 let selectorList: string[] = []; if (Array.isArray(selector)) { selectorList = selectorList.concat(selector.filter((it) => typeof it === "string" && it.toString() !== "")); } else if (typeof selector === "string") { selectorList.push(selector); } /** * 事件的回调函数 */ let listenerCallBack: (this: HTMLElement, event: T, $selector: HTMLElement) => void = callback as ( this: HTMLElement, event: Event, $selector?: HTMLElement ) => void | boolean; /** * 事件的配置 */ let listenerOption: EventListenerOptions = { capture: false, }; if (typeof selector === "function") { // 这是为没有selector的情况 // 那么它就是callback listenerCallBack = selector; listenerOption = getOption(args, 3, listenerOption); } else { // 这是存在selector的情况 listenerOption = getOption(args, 4, listenerOption); } if (args.length === 5 && typeof args[4] === "function" && typeof filter !== "function") { // 目标函数、事件名、回调函数、事件配置、过滤函数 filter = option as ( value: PopsDOMUtilsEventListenerOptionsAttribute, index: number, array: PopsDOMUtilsEventListenerOptionsAttribute[] ) => boolean; } $elList.forEach(($elItem) => { // 获取对象上的事件 const elementEvents: { [key: string]: PopsDOMUtilsEventListenerOptionsAttribute[]; } = Reflect.get($elItem, GlobalData.domEventSymbol) || {}; eventTypeList.forEach((eventName) => { const handlers = elementEvents[eventName] || []; // 过滤出需要删除的事件 const handlersFiltered = typeof filter === "function" ? handlers.filter(filter) : handlers; for (let index = 0; index < handlersFiltered.length; index++) { const handler = handlersFiltered[index]; // 过滤出的事件再根据下面的条件进行判断处理移除 // 1. callback内存地址必须相同 // 2. selector必须相同 // 3. option.capture必须相同 let flag = true; if (flag && listenerCallBack && handler.callback !== listenerCallBack) { 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) { $elItem.removeEventListener(eventName, handler.handlerCallBack, handler.option); for (let i = handlers.length - 1; i >= 0; i--) { if (handlers[i] === handler) { handlers.splice(i, 1); } } } } if (handlers.length === 0) { // 如果没有任意的handler,那么删除该属性 CommonUtils.delete(elementEvents, eventType); if (Object.keys(elementEvents).length === 0) { CommonUtils.delete($elItem, GlobalData.domEventSymbol); } } }); Reflect.set($elItem, GlobalData.domEventSymbol, elementEvents); }); } /** * 取消绑定所有的事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType (可选)需要取消监听的事件,不传入该参数则遍历所有监听的事件 */ offAll(element: PopsDOMUtilsElementEventType, eventType?: string): void; /** * 取消绑定所有的事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType (可选)需要取消监听的事件,不传入该参数则遍历所有监听的事件 */ offAll(element: PopsDOMUtilsElementEventType, eventType?: PopsDOMUtils_EventType | PopsDOMUtils_EventType[]): void; /** * 取消绑定所有的事件 * @param element 需要取消绑定的元素|元素数组 * @param eventType (可选)需要取消监听的事件,不传入该参数则遍历所有监听的事件 */ offAll( element: PopsDOMUtilsElementEventType, eventType?: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string ) { const that = this; if (typeof element === "string") { element = that.selectorAll(element); } if (element == null) { return; } let $elList: (Element | Document | Window | typeof globalThis | Node | ChildNode | EventTarget)[] = []; if (element instanceof NodeList || Array.isArray(element)) { $elList = $elList.concat(Array.from(element as HTMLElement[])); } else { $elList.push(element); } let eventTypeList: string[] = []; if (Array.isArray(eventType)) { eventTypeList = eventTypeList.concat(eventType as string[]); } else if (typeof eventType === "string") { eventTypeList = eventTypeList.concat(eventType.split(" ")); } $elList.forEach(($elItem) => { const symbolList = [...new Set([...Object.getOwnPropertySymbols($elItem), GlobalData.domEventSymbol])]; symbolList.forEach((__symbol__) => { if (!__symbol__.toString().startsWith("Symbol(events_")) { return; } const elementEvents: { [key: string]: PopsDOMUtilsEventListenerOptionsAttribute[]; } = Reflect.get($elItem, __symbol__) || {}; const iterEventNameList = eventTypeList.length ? eventTypeList : Object.keys(elementEvents); iterEventNameList.forEach((eventName) => { const handlers: PopsDOMUtilsEventListenerOptionsAttribute[] = elementEvents[eventName]; if (!handlers) { return; } for (const handler of handlers) { $elItem.removeEventListener(eventName, handler.handlerCallBack, { capture: handler.option.capture, }); } const events = Reflect.get($elItem, __symbol__); CommonUtils.delete(events, eventName); if (Object.keys(events).length === 0) { CommonUtils.delete($elItem, __symbol__); } }); }); }); } /** * 等待文档加载完成后执行指定的函数 * @param callback 需要执行的函数 * @example * DOMUtils.onReady(function(){ * console.log("文档加载完毕") * }) */ onReady any>(callback: T) { const that = this; if (typeof callback !== "function") { return; } /** * 检测文档是否加载完毕 */ function checkDOMReadyState() { try { if ( document.readyState === "complete" || (document.readyState !== "loading" && !(document.documentElement as any).doScroll) ) { return true; } else { return false; } } catch { return false; } } /** * 成功加载完毕后触发的回调函数 */ function completed() { removeDomReadyListener(); callback(); } const targetList = [ { target: PopsCore.document, eventType: "DOMContentLoaded", callback: completed, }, { target: PopsCore.window, eventType: "load", callback: completed, }, ]; /** * 添加监听 */ function addDomReadyListener() { for (let index = 0; index < targetList.length; index++) { const item = targetList[index]; that.on(item.target, item.eventType, item.callback); } } /** * 移除监听 */ function removeDomReadyListener() { for (let index = 0; index < targetList.length; index++) { const item = targetList[index]; that.off(item.target, item.eventType, item.callback); } } if (checkDOMReadyState()) { // 检查document状态 popsUtils.setTimeout(callback, 0); } else { // 添加监听 addDomReadyListener(); } } /** * 主动触发事件 * @param element 需要触发的元素|元素数组|window * @param eventType 需要触发的事件 * @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback,但是这种会让使用了$selector的没有值 * @example * // 触发元素a.xx的click事件 * DOMUtils.emit(document.querySelector("a.xx"),"click") * DOMUtils.emit("a.xx","click") * // 触发元素a.xx的click、tap、hover事件 * DOMUtils.emit(document.querySelector("a.xx"),"click tap hover") * DOMUtils.emit("a.xx",["click","tap","hover"]) */ emit( element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | any[] | typeof globalThis | Window | Document, eventType: string | string[], useDispatchToTriggerEvent?: boolean ): void; /** * 主动触发事件 * @param element 需要触发的元素|元素数组|window * @param eventType 需要触发的事件 * @param extraDetails 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象 * @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值 * @example * // 触发元素a.xx的click事件 * DOMUtils.emit(document.querySelector("a.xx"),"click") * DOMUtils.emit("a.xx","click") * // 触发元素a.xx的click、tap、hover事件 * DOMUtils.emit(document.querySelector("a.xx"),"click tap hover") * DOMUtils.emit("a.xx",["click","tap","hover"]) */ emit( element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | any[] | typeof globalThis | Window | Document, eventType: string | string[], extraDetails?: object, useDispatchToTriggerEvent?: boolean ): void; /** * 主动触发事件 * @param element 需要触发的元素|元素数组|window * @param eventType 需要触发的事件 * @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值 * @example * // 触发元素a.xx的click事件 * DOMUtils.emit(document.querySelector("a.xx"),"click") * DOMUtils.emit("a.xx","click") * // 触发元素a.xx的click、tap、hover事件 * DOMUtils.emit(document.querySelector("a.xx"),"click tap hover") * DOMUtils.emit("a.xx",["click","tap","hover"]) */ emit( element: Element | string | NodeList | any[] | Window | Document, eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[], useDispatchToTriggerEvent?: boolean ): void; /** * 主动触发事件 * @param element 需要触发的元素|元素数组|window * @param event 触发的事件 * @param extraDetails (可选)赋予触发的Event的额外属性 * @param useDispatchToTriggerEvent (可选)是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值 * @example * DOMUtils.emit("a.xx", new Event("click")) * @example * DOMUtils.emit("a.xx", new Event("click"), { * disableHook: true * }) * @example * DOMUtils.emit("a.xx", new Event("click"), { * disableHook: true * },false) */ emit( element: Element | string | NodeList | any[] | Window | Document, event: Event, extraDetails?: object, useDispatchToTriggerEvent?: boolean ): void; /** * 主动触发事件 * @param element 需要触发的元素|元素数组|window * @param eventType 需要触发的事件 * @param extraDetails 赋予触发的Event的额外属性,如果是Event类型,那么将自动代替默认new的Event对象 * @param useDispatchToTriggerEvent 是否使用dispatchEvent来触发事件,默认true,如果为false,则直接调用通过.on监听的callback(),但是这种只有一个入参,如果使用$selector则没有值 * @example * // 触发元素a.xx的click事件 * DOMUtils.emit(document.querySelector("a.xx"),"click") * DOMUtils.emit("a.xx","click") * // 触发元素a.xx的click、tap、hover事件 * DOMUtils.emit(document.querySelector("a.xx"),"click tap hover") * DOMUtils.emit("a.xx",["click","tap","hover"]) */ emit( element: Element | string | NodeList | any[] | Window | Document, eventType: PopsDOMUtils_EventType | PopsDOMUtils_EventType[] | string | string[] | Event, extraDetails?: object | boolean, useDispatchToTriggerEvent: boolean = true ) { const that = this; if (typeof element === "string") { element = that.selectorAll(element); } if (element == null) { return; } let $elList: (Document | Window | Element)[] = []; if (element instanceof NodeList || Array.isArray(element)) { $elList = $elList.concat(Array.from(element)); } else { $elList.push(element); } /** * 主动添加属性 */ const addExtraProp = (event: Event, obj: any) => { if (event instanceof Event && typeof obj === "object" && obj != null && !Array.isArray(obj)) { const detailKeys = Object.keys(obj); detailKeys.forEach((keyName) => { const value = Reflect.get(obj, keyName); // 在event上添加属性 Reflect.set(event, keyName, value); }); } }; let eventTypeList: string[] = []; /** * 主动传递的事件 */ let __event__: Event | null = null; if (Array.isArray(eventType)) { eventTypeList = eventType.filter((it) => typeof it === "string" && it.trim() !== ""); } else if (typeof eventType === "string") { eventTypeList = eventType.split(" "); } else if (eventType instanceof Event) { __event__ = eventType; addExtraProp(__event__, extraDetails); } $elList.forEach(($elItem) => { /* 获取对象上的事件 */ const elementEvents: { [key: string]: PopsDOMUtilsEventListenerOptionsAttribute[]; } = Reflect.get($elItem, GlobalData.domEventSymbol) || {}; /** * 触发事件 */ const dispatchEvent = (event: Event, eventTypeItem: string) => { if (useDispatchToTriggerEvent == false && eventTypeItem in elementEvents) { // 直接调用.on监听的事件 elementEvents[eventTypeItem].forEach((eventsItem) => { eventsItem.handlerCallBack(event); }); } else { $elItem.dispatchEvent(event); } }; if (__event__) { // 使用主动传递的事件直接触发 const event = __event__; const eventTypeItem = event.type; dispatchEvent(event, eventTypeItem); } else { eventTypeList.forEach((eventTypeItem) => { // 构造事件 const event = new Event(eventTypeItem); addExtraProp(event, extraDetails); dispatchEvent(event, eventTypeItem); }); } }); } /** * 当按键松开时触发事件 * 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("按键松开"); * }) */ onKeyup( element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | Window | Node | typeof globalThis, handler: (this: HTMLElement, event: PopsDOMUtils_Event["keyup"]) => void, option?: boolean | PopsDOMUtilsEventListenerOption ) { const that = this; if (element == null) { return; } if (typeof element === "string") { element = that.selectorAll(element); } if (CommonUtils.isNodeList(element)) { // 设置 const listenerList: (PopsDOMUtilsAddEventListenerResult | undefined)[] = []; element.forEach(($ele) => { const listener = that.onKeyup($ele as HTMLElement, handler, option); listenerList.push(listener); }); return { off() { listenerList.forEach((listener) => { if (!listener) { return; } listener.off(); }); }, } as PopsDOMUtilsAddEventListenerResult; } 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("按键按下"); * }) */ onKeydown( element: PopsDOMUtilsTargetElementType | Element | DocumentFragment | Window | Node | typeof globalThis, handler: (this: HTMLElement, event: PopsDOMUtils_Event["keydown"]) => void, option?: boolean | PopsDOMUtilsEventListenerOption ) { const that = this; if (element == null) { return; } if (typeof element === "string") { element = that.selectorAll(element); } if (CommonUtils.isNodeList(element)) { // 设置 const listenerList: (PopsDOMUtilsAddEventListenerResult | undefined)[] = []; element.forEach(($ele) => { const listener = that.onKeydown($ele as HTMLElement, handler, option); listenerList.push(listener); }); return { off() { listenerList.forEach((listener) => { if (!listener) { return; } listener.off(); }); }, } as PopsDOMUtilsAddEventListenerResult; } return that.on(element, "keydown", null, handler, option); } /** * 阻止事件传递 * * + `.preventDefault()`: 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL,或者在输入框内输入文字 * + `.stopPropagation()`: 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素 * + `.stopImmediatePropagation()`: 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发 * @param event 要阻止传递的事件 * @example * DOMUtils.preventEvent(event); */ preventEvent(event: Event): false; /** * 阻止事件传递 * @param event 要阻止传递的事件 * @param onlyStopPropagation (可选)是否仅阻止事件的传播,默认false,不调用`.preventDefault()` * @example * DOMUtils.preventEvent(event, true); */ preventEvent(event: Event, onlyStopPropagation: T): T extends true ? void : false; /** * 通过监听事件来主动阻止事件的传递 * @param $el 要进行处理的元素 * @param eventNameList 要阻止的事件名|列表 * @param option (可选)配置项 * @example * DOMUtils.preventEvent(document.querySelector("a"), "click") * @example * DOMUtils.preventEvent(document.querySelector("a"), "click", undefined, { * capture: true, * }) * @example * DOMUtils.preventEvent(document, "click", "a.xxx", { * capture: true, * onlyStopPropagation: true, * }) */ preventEvent( $el: Element | Document | ShadowRoot, eventNameList: string | string[], option?: { /** (可选)是否捕获,默认false */ capture?: boolean; /** (可选)是否仅阻止事件的传播,默认false,不调用`.preventDefault()` */ onlyStopPropagation?: boolean; } ): { /** 移除监听事件 */ off(): void; }; /** * 通过监听事件来主动阻止事件的传递 * @param $el 要进行处理的元素 * @param eventNameList 要阻止的事件名|列表 * @param selector 子元素选择器 * @param option (可选)配置项 * @example * DOMUtils.preventEvent(document.querySelector("a"), "click") * @example * DOMUtils.preventEvent(document.querySelector("a"), "click", undefined, { * capture: true, * }) * @example * DOMUtils.preventEvent(document, "click", "a.xxx", { * capture: true, * onlyStopPropagation: true, * }) */ preventEvent( $el: Element | Document | ShadowRoot, eventNameList: string | string[], selector: string | string[] | null | undefined, option?: { /** (可选)是否捕获,默认false */ capture?: boolean; /** (可选)是否仅阻止事件的传播,默认false,不调用`.preventDefault()` */ onlyStopPropagation?: boolean; } ): { /** 移除监听事件 */ off(): void; }; preventEvent(...args: any[]): boolean | void | { off(): void } { /** * 阻止事件的默认行为发生,并阻止事件传播 */ const stopEvent = (event: Event, onlyStopPropagation?: boolean) => { // 停止事件的传播,阻止它继续向更上层的元素冒泡,事件将不会再传播给其他的元素 event?.stopPropagation(); // 阻止事件传播,并且还能阻止元素上的其他事件处理程序被触发 event?.stopImmediatePropagation(); if (typeof onlyStopPropagation === "boolean" && onlyStopPropagation) { return; } // 阻止事件的默认行为发生。例如,当点击一个链接时,浏览器会默认打开链接的URL,或者在输入框内输入文字 event?.preventDefault(); return false; }; if (args[0] instanceof Event) { // 直接阻止事件 const onlyStopPropagation: boolean = args[1]; return stopEvent(args[0], onlyStopPropagation); } else { const $el: Element | Document | ShadowRoot = args[0]; let eventNameList: string | string[] = args[1]; let selector: string | string[] | null | undefined = void 0; let capture = false; let onlyStopPropagation = false; // 添加对应的事件来阻止触发 if (typeof eventNameList === "string") { eventNameList = [eventNameList]; } let option: | { capture?: boolean; onlyStopPropagation?: boolean; } | undefined = void 0; if (args.length === 2) { // ignore } else if (typeof args[2] === "string" || Array.isArray(args[2])) { // selector selector = args[2]; if (typeof args[3] === "object" && args[3] != null) { option = args[3]; } } else if (typeof args[2] === "object" && args[2] != null && !Array.isArray(args[2])) { // option option = args[2]; } else { throw new TypeError("Invalid argument"); } if (option) { capture = Boolean(option.capture); onlyStopPropagation = Boolean(option.onlyStopPropagation); } const listener = this.on( $el, eventNameList, selector, (evt) => { return stopEvent(evt, onlyStopPropagation); }, { capture: capture } ); return listener; } } /** * 选择器,可使用以下的额外语法 * * + :contains([text]) 作用: 找到包含指定文本内容的指定元素 * + :empty 作用:找到既没有文本内容也没有子元素的指定元素 * + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素 * @param selector 选择器 * @example * DOMUtils.selector("div:contains('测试')") * > div.xxx * @example * DOMUtils.selector("div:empty") * > div.xxx * @example * DOMUtils.selector("div:regexp('^xxxx$')") * > div.xxx */ selector(selector: K): HTMLElementTagNameMap[K] | undefined; selector(selector: string): E | undefined; selector(selector: string) { return this.selectorAll(selector)[0]; } /** * 选择器,可使用以下的额外语法 * * + :contains([text]) 作用: 找到包含指定文本内容的指定元素 * + :empty 作用:找到既没有文本内容也没有子元素的指定元素 * + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素 * @param selector 选择器 * @example * DOMUtils.selectorAll("div:contains('测试')") * > [div.xxx] * @example * DOMUtils.selectorAll("div:empty") * > [div.xxx] * @example * DOMUtils.selectorAll("div:regexp('^xxxx$')") * > [div.xxx] * @example * DOMUtils.selectorAll("div:regexp(/^xxx/ig)") * > [div.xxx] */ selectorAll(selector: K): HTMLElementTagNameMap[K][]; selectorAll(selector: string): E[]; selectorAll(selector: string) { selector = selector.trim(); if (selector.match(/[^\s]{1}:empty$/gi)) { // empty 语法 selector = selector.replace(/:empty$/gi, ""); return Array.from(PopsCore.document.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(PopsCore.document.querySelectorAll(selector)).filter(($ele) => { 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(PopsCore.document.querySelectorAll(selector)).filter(($ele) => { return Boolean(($ele?.textContent || ($ele)?.innerText)?.match(regexp)); }); } else { // 普通语法 return Array.from(PopsCore.document.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: HTMLElement | Element | null | undefined, selector: string): boolean { 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, ""); 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, ""); let content = $el?.textContent || ($el)?.innerText; if (typeof content !== "string") { content = ""; } return $el.matches(selector) && Boolean(content?.match(regexp)); } else { // 普通语法 return $el.matches(selector); } } /** * 根据选择器获取上层元素,可使用以下的额外语法 * * + :contains([text]) 作用: 找到包含指定文本内容的指定元素 * + :empty 作用:找到既没有文本内容也没有子元素的指定元素 * + :regexp([text]) 作用: 找到符合正则表达式的内容的指定元素 * @param $el 元素 * @param selector 选择器 * @example * DOMUtils.closest("div:contains('测试')") * > div.xxx * @example * DOMUtils.closest("div:empty") * > div.xxx * @example * DOMUtils.closest("div:regexp('^xxxx$')") * > div.xxxx * @example * DOMUtils.closest("div:regexp(/^xxx/ig)") * > null */ closest( $el: HTMLElement | Element, selector: string ): HTMLElementTagNameMap[K] | null; closest($el: HTMLElement | Element, selector: string): E | null; closest($el: HTMLElement | Element, selector: string): E | null { 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) { 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) { 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; } } /** * 监input、textarea的输入框值改变的事件 */ onInput( $el: HTMLInputElement | HTMLTextAreaElement, callback: (evt: InputEvent) => void | Promise, option?: PopsDOMUtilsEventListenerOption | boolean ) { /** * 是否正在输入中 */ let isComposite = false; const __callback = async (event: InputEvent) => { if (isComposite) return; await callback(event); }; const __composition_start_callback = () => { isComposite = true; }; const __composition_end_callback = () => { isComposite = false; this.emit($el, "input", { isComposite, }); }; const inputListener = this.on($el, "input", __callback, option); const compositionStartListener = this.on($el, "compositionstart", __composition_start_callback, option); const compositionEndListener = this.on($el, "compositionend", __composition_end_callback, option); return { off: () => { inputListener.off(); compositionStartListener.off(); compositionEndListener.off(); }, }; } } class PopsDOMUtils extends PopsDOMUtilsEvent { /** 获取 animationend 在各个浏览器的兼容名 */ getAnimationEndNameList() { return ["webkitAnimationEnd", "mozAnimationEnd", "MSAnimationEnd", "oanimationend", "animationend"]; } /** 获取 transitionend 在各个浏览器的兼容名 */ getTransitionEndNameList() { return ["webkitTransitionEnd", "mozTransitionEnd", "MSTransitionEnd", "otransitionend", "transitionend"]; } /** * 实现jQuery中的$().offset(); * @param element * @param calcScroll 计算滚动距离 */ offset(element: HTMLElement, calcScroll: boolean = true) { const rect = element.getBoundingClientRect(); const win = element.ownerDocument.defaultView; const resultRect = new DOMRect( calcScroll ? parseFloat((rect.left + (win?.pageXOffset || 0)).toString()) : rect.left, calcScroll ? parseFloat((rect.top + (win?.pageYOffset || 0)).toString()) : rect.top, rect.width, rect.height ); return resultRect; } /** * 获取元素的宽度 * @param element 要获取宽度的元素 * @param isShow 是否已进行isShow,避免爆堆栈 * @param parent 用于判断是否已显示的父元素载体 * @returns 元素的宽度,单位为像素 * @example * // 获取元素a.xx的宽度 * DOMUtils.width(document.querySelector("a.xx")) * DOMUtils.width("a.xx") * > 100 * // 获取window的宽度 * DOMUtils.width(window) * > 400 * @example * // 设置元素a.xx的宽度为200 * DOMUtils.width(document.querySelector("a.xx"),200) * DOMUtils.width("a.xx",200) */ width( element: HTMLElement | string | Window | Document | typeof globalThis, isShow?: boolean, parent?: HTMLElement | ShadowRoot ): number; width( element: HTMLElement | string | Window | Document | typeof globalThis, isShow: boolean = false, parent?: HTMLElement | ShadowRoot ) { const DOMUtilsContext = this; if (typeof element === "string") { element = this.selector(element) as HTMLElement; } if (element == null) { return; } if (popsUtils.isWin(element)) { return PopsCore.window.document.documentElement.clientWidth; } if ((element as HTMLElement).nodeType === 9) { // Document文档节点 element = element as Document; return Math.max( element.body.scrollWidth, element.documentElement.scrollWidth, element.body.offsetWidth, element.documentElement.offsetWidth, element.documentElement.clientWidth ); } if (isShow || (!isShow && popsDOMUtils.isShow(element as HTMLElement))) { // 已显示 // 不从style中获取对应的宽度,因为可能使用了class定义了width !important element = element as HTMLElement; // 如果element.style.width为空 则从css里面获取是否定义了width信息如果定义了 则读取css里面定义的宽度width if (parseFloat(popsDOMUtils.getStyleValue(element, "width").toString()) > 0) { return parseFloat(popsDOMUtils.getStyleValue(element, "width").toString()); } // 如果从css里获取到的值不是大于0 可能是auto 则通过offsetWidth来进行计算 if (element.offsetWidth > 0) { const borderLeftWidth = popsDOMUtils.getStyleValue(element, "borderLeftWidth"); const borderRightWidth = popsDOMUtils.getStyleValue(element, "borderRightWidth"); const paddingLeft = popsDOMUtils.getStyleValue(element, "paddingLeft"); const paddingRight = popsDOMUtils.getStyleValue(element, "paddingRight"); const backHeight = parseFloat(element.offsetWidth.toString()) - parseFloat(borderLeftWidth.toString()) - parseFloat(borderRightWidth.toString()) - parseFloat(paddingLeft.toString()) - parseFloat(paddingRight.toString()); return parseFloat(backHeight.toString()); } return 0; } else { // 未显示 element = element as HTMLElement; const { cloneNode, recovery } = popsDOMUtils.showElement(element, parent); const width = DOMUtilsContext.width(cloneNode, true, parent); recovery(); return width; } } /** * 获取元素的高度 * @param element 要获取高度的元素 * @param isShow 是否已进行isShow,避免爆堆栈 * @param parent 用于判断是否已显示的父元素载体 * @returns 元素的高度,单位为像素 * @example * // 获取元素a.xx的高度 * DOMUtils.height(document.querySelector("a.xx")) * DOMUtils.height("a.xx") * > 100 * // 获取window的高度 * DOMUtils.height(window) * > 700 * @example * // 设置元素a.xx的高度为200 * DOMUtils.height(document.querySelector("a.xx"),200) * DOMUtils.height("a.xx",200) */ height( element: HTMLElement | string | Window | Document | typeof globalThis, isShow?: boolean, parent?: HTMLElement | ShadowRoot ): number; height( element: HTMLElement | string | Window | Document | typeof globalThis, isShow: boolean = false, parent?: HTMLElement | ShadowRoot ) { const DOMUtilsContext = this; if (popsUtils.isWin(element)) { return PopsCore.window.document.documentElement.clientHeight; } if (typeof element === "string") { element = this.selector(element) as HTMLElement; } if (element == null) { return; } if ((element as Document).nodeType === 9) { element = element as Document; // Document文档节点 return Math.max( element.body.scrollHeight, element.documentElement.scrollHeight, element.body.offsetHeight, element.documentElement.offsetHeight, element.documentElement.clientHeight ); } if (isShow || (!isShow && popsDOMUtils.isShow(element as HTMLElement))) { element = element as HTMLElement; // 已显示 // 从style中获取对应的高度,因为可能使用了class定义了width !important // 如果element.style.height为空 则从css里面获取是否定义了height信息如果定义了 则读取css里面定义的高度height if (parseFloat(popsDOMUtils.getStyleValue(element, "height").toString()) > 0) { return parseFloat(popsDOMUtils.getStyleValue(element, "height").toString()); } // 如果从css里获取到的值不是大于0 可能是auto 则通过offsetHeight来进行计算 if (element.offsetHeight > 0) { const borderTopWidth = popsDOMUtils.getStyleValue(element, "borderTopWidth"); const borderBottomWidth = popsDOMUtils.getStyleValue(element, "borderBottomWidth"); const paddingTop = popsDOMUtils.getStyleValue(element, "paddingTop"); const paddingBottom = popsDOMUtils.getStyleValue(element, "paddingBottom"); const backHeight = parseFloat(element.offsetHeight.toString()) - parseFloat(borderTopWidth.toString()) - parseFloat(borderBottomWidth.toString()) - parseFloat(paddingTop.toString()) - parseFloat(paddingBottom.toString()); return parseFloat(backHeight.toString()); } return 0; } else { // 未显示 element = element as HTMLElement; const { cloneNode, recovery } = popsDOMUtils.showElement(element, parent); const height = DOMUtilsContext.height(cloneNode, true, parent); recovery(); return height; } } /** * 获取元素的外部宽度(包括边框和外边距) * @param element 要获取外部宽度的元素 * @param 是否已进行isShow,避免爆堆栈 * @param parent 用于判断是否已显示的父元素载体 * @returns 元素的外部宽度,单位为像素 * @example * // 获取元素a.xx的外部宽度 * DOMUtils.outerWidth(document.querySelector("a.xx")) * DOMUtils.outerWidth("a.xx") * > 100 * // 获取window的外部宽度 * DOMUtils.outerWidth(window) * > 400 */ outerWidth( element: HTMLElement | string | Window | Document, isShow?: boolean, parent?: HTMLElement | ShadowRoot ): number; outerWidth( element: HTMLElement | string | Window | Document, isShow: boolean = false, parent?: HTMLElement | ShadowRoot ) { const DOMUtilsContext = this; if (popsUtils.isWin(element)) { return PopsCore.window.innerWidth; } if (typeof element === "string") { element = this.selector(element) as HTMLElement; } if (element == null) { return; } element = element as HTMLElement; if (isShow || (!isShow && popsDOMUtils.isShow(element))) { const style = getComputedStyle(element, null); const marginLeft = popsDOMUtils.getStyleValue(style, "marginLeft"); const marginRight = popsDOMUtils.getStyleValue(style, "marginRight"); return element.offsetWidth + marginLeft + marginRight; } else { const { cloneNode, recovery } = popsDOMUtils.showElement(element, parent); const outerWidth = DOMUtilsContext.outerWidth(cloneNode, true, parent); recovery(); return outerWidth; } } /** * 获取元素的外部高度(包括边框和外边距) * @param element 要获取外部高度的元素 * @param isShow 是否已进行isShow,避免爆堆栈 * @param parent 用于判断是否已显示的父元素载体 * @returns 元素的外部高度,单位为像素 * @example * // 获取元素a.xx的外部高度 * DOMUtils.outerHeight(document.querySelector("a.xx")) * DOMUtils.outerHeight("a.xx") * > 100 * // 获取window的外部高度 * DOMUtils.outerHeight(window) * > 700 */ outerHeight(element: HTMLElement | string | Window, isShow?: boolean, parent?: HTMLElement | ShadowRoot): number; outerHeight( element: HTMLElement | string | Window, isShow: boolean = false, parent?: HTMLElement | ShadowRoot ): number { const DOMUtilsContext = this; if (popsUtils.isWin(element)) { return PopsCore.window.innerHeight; } if (typeof element === "string") { element = this.selector(element) as HTMLElement; } element = element as HTMLElement; if (isShow || (!isShow && popsDOMUtils.isShow(element))) { const style = getComputedStyle(element, null); const marginTop = popsDOMUtils.getStyleValue(style, "marginTop"); const marginBottom = popsDOMUtils.getStyleValue(style, "marginBottom"); return element.offsetHeight + marginTop + marginBottom; } else { const { cloneNode, recovery } = popsDOMUtils.showElement(element, parent); const outerHeight = DOMUtilsContext.outerHeight(cloneNode, true, parent); recovery(); return outerHeight; } } /** * 添加className * @param $el 目标元素 * @param args className属性 */ addClassName( $el: Element | undefined | null, ...args: (string | string[] | (() => string | string[]) | undefined | null)[] ) { if ($el == null) return; if (!($el instanceof Element)) return; if (args.length === 0) return; args.forEach((argItem) => { if (argItem == null) return; if (Array.isArray(argItem)) { this.addClassName($el, ...argItem); return; } if (typeof argItem === "function") { argItem = argItem(); } if (typeof argItem !== "string") { // 不是字符串 return; } if (argItem.trim() === "") { // 空字符串 return; } const classNameList = argItem.split(" ").filter((item) => item.trim() !== ""); $el.classList.add(...classNameList); }); } /** * 删除className * @param $el 目标元素 * @param args className属性 */ removeClassName( $el: Element | undefined | null, ...args: (string | string[] | (() => string | string[]) | undefined | null)[] ) { if ($el == null) return; if (!($el instanceof Element)) return; if (args.length === 0) return; args.forEach((argItem) => { if (argItem == null) return; if (Array.isArray(argItem)) { this.removeClassName($el, ...argItem); return; } if (typeof argItem === "function") { argItem = argItem(); } if (typeof argItem !== "string") { // 不是字符串 return; } if (argItem.trim() === "") { // 空字符串 return; } const classNameList = argItem.split(" ").filter((item) => item.trim() !== ""); $el.classList.remove(...classNameList); }); } /** * 判断元素是否包含某个className * @param $el 目标元素 * @param className className属性 */ containsClassName($el: Element | undefined | null, className: string): boolean { if ($el == null) { return false; } if (typeof className !== "string") { return false; } if (className.trim() === "") { return false; } return $el.classList.contains(className); } /** * 获取元素的样式属性值 * @param $el 目标元素 * @param property 样式属性名或包含多个属性名和属性值的对象 * @example * // 获取元素a.xx的CSS属性display * DOMUtils.css(document.querySelector("a.xx"),"display"); * DOMUtils.css("a.xx","display"); * > "none" * */ css($el: PopsDOMUtilsTargetElementType, property: PopsDOMUtilsCSSPropertyType): string; /** * 获取元素的样式属性值 * @param $el 目标元素 * @param property 样式属性名或包含多个属性名和属性值的对象 * @example * // 获取元素a.xx的CSS属性display * DOMUtils.css(document.querySelector("a.xx"),"display"); * DOMUtils.css("a.xx","display"); * > "none" * */ css($el: PopsDOMUtilsTargetElementType, property: string): string; /** * 设置元素的样式属性 * @param $el 目标元素 * @param property 样式属性名或包含多个属性名和属性值的对象 * @param value 样式属性值 * @example * // 设置元素a.xx的CSS属性display为block * DOMUtils.css(document.querySelector("a.xx"),"display","block"); * DOMUtils.css(document.querySelector("a.xx"),"display","block !important"); * DOMUtils.css("a.xx","display","block"); * DOMUtils.css("a.xx","display","block !important"); * @example * // 设置元素a.xx的CSS属性top为10px * DOMUtils.css(document.querySelector("a.xx"),"top","10px"); * DOMUtils.css(document.querySelector("a.xx"),"top",10); * */ css( $el: PopsDOMUtilsTargetElementType, property: PopsDOMUtilsCSSPropertyType & string, value: string | number ): string; /** * 设置元素的样式属性 * @param $el 目标元素 * @param property 样式属性名或包含多个属性名和属性值的对象 * @param value 样式属性值 * @example * // 设置元素a.xx的CSS属性display为block * DOMUtils.css(document.querySelector("a.xx"),{ display: "block" }}); * DOMUtils.css(document.querySelector("a.xx"),{ display: "block !important" }}); * @example * // 设置元素a.xx的CSS属性top为10px * DOMUtils.css(document.querySelector("a.xx"),{ top: "10px" }); * DOMUtils.css(document.querySelector("a.xx"),{ top: 10 }); * */ css( $el: PopsDOMUtilsTargetElementType, property: | PopsDOMUtilsCSSProperty | { [key: string]: string | number; } | string ): string; css( $el: PopsDOMUtilsTargetElementType, property: PopsDOMUtilsCSSPropertyType | string | PopsDOMUtilsCSSProperty, value?: string | number ) { const that = this; /** * 把纯数字没有px的加上 */ function handlePixe(propertyName: string, propertyValue: string | number) { 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 (Array.isArray($el) || $el instanceof NodeList) { if (typeof property === "string") { if (value == null) { // 获取属性 return that.css($el[0] as HTMLElement, property); } else { // 设置属性 $el.forEach(($elItem) => { that.css($elItem as HTMLElement, property); }); return; } } else if (typeof property === "object") { // 设置属性 $el.forEach(($elItem) => { that.css($elItem as HTMLElement, property as PopsDOMUtilsCSSProperty); }); return; } return; } const setStyleProperty = (propertyName: string, propertyValue: string | number) => { 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 PopsCore.globalThis.getComputedStyle($el).getPropertyValue(property); } else { setStyleProperty(property, value); } } else if (typeof property === "object") { for (const prop in property) { const value = property[prop as keyof typeof property]; setStyleProperty(prop, value!); } } else { // 其他情况 throw new TypeError("property must be string or object"); } } /** * 创建元素 * @param tagName 标签名 * @param property 属性 * @param attributes 元素上的自定义属性 * @example * // 创建一个DIV元素,且属性class为xxx * DOMUtils.createElement("div",undefined,{ class:"xxx" }); * >
* @example * // 创建一个DIV元素 * DOMUtils.createElement("div"); * >
* @example * // 创建一个DIV元素 * DOMUtils.createElement("div","测试"); * >
测试
*/ createElement( /** 元素名 */ tagName: K, /** 属性 */ property?: | ({ [P in keyof HTMLElementTagNameMap[K]]?: HTMLElementTagNameMap[K][P] extends string | boolean | number ? HTMLElementTagNameMap[K][P] : never; } & { [key: string]: any; }) | string, /** 自定义属性 */ attributes?: PopsDOMUtilsCreateElementAttributesMap ): HTMLElementTagNameMap[K] { const $temp = PopsCore.document.createElement(tagName); if (typeof property === "string") { PopsSafeUtils.setSafeHTML($temp, property); return $temp; } if (property == null) { property = {}; } if (attributes == null) { attributes = {}; } Object.keys(property).forEach((key) => { const value = property[key]; if (key === "innerHTML") { PopsSafeUtils.setSafeHTML($temp, value); return; } Reflect.set($temp, 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(); } $temp.setAttribute(key, value); }); return $temp; } /** * 字符串转HTMLElement * @param elementString * @returns */ parseTextToDOM(elementString: string): R { // 去除前后的换行和空格 elementString = elementString.replace(/^[\n|\s]*/g, "").replace(/[\n|\s]*$/g, ""); const targetElement = this.createElement("div", { innerHTML: elementString, }); return targetElement.firstChild as any as R; } /** * 获取文字的位置信息 * @param input 输入框 * @param selectionStart 起始位置 * @param selectionEnd 结束位置 * @param debug 是否是调试模式 * + true 不删除临时节点元素 * + false 删除临时节点元素 */ getTextBoundingRect( input: HTMLInputElement | HTMLTextAreaElement, selectionStart: number | string, selectionEnd: number | string, debug: boolean ): DOMRect { // Basic parameter validation if (!input || !("value" in input)) return input; 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 as any).createTextRange == "function") { const range = (input as any).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(); let topPos = offset.top; let leftPos = offset.left; const width = getInputCSS("width", true); const height = getInputCSS("height", true); // 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 i = 0; i < listOfModifiers.length; i++) { const property = listOfModifiers[i]; cssDefaultStyles += property + ":" + getInputCSS(property, false) + ";"; } // End of CSS variable checks // 不能为空,不然获取不到高度 const text = input.value || "G", textLen = text.length, fakeClone = 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"; PopsCore.document.body.appendChild(fakeClone); const returnValue = fakeRange.getBoundingClientRect(); //Get rect if (!debug) fakeClone.parentNode!.removeChild(fakeClone); //Remove temp return returnValue; // Local functions for readability of the previous code /** * * @param start * @param end */ function appendPart(start: number, end: number): HTMLSpanElement { const span = 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 = document.body, win = document.defaultView, docElem = document.documentElement, box = 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 */ function getInputCSS(prop: string, isnumber: T): T extends true ? number : string { const val = PopsCore.document.defaultView!.getComputedStyle(input, null).getPropertyValue(prop); if (isnumber) { return parseFloat(val) as T extends true ? number : string; } else { return val as T extends true ? number : string; } } } /** * 使用className来隐藏元素 * @param ele * @param isImportant 是否使用!important */ cssHide(ele: Element | null, isImportant = false) { if (ele == null) { return; } if (isImportant) { popsDOMUtils.addClassName(ele, PopsCommonCSSClassName.hideImportant); } else { popsDOMUtils.addClassName(ele, PopsCommonCSSClassName.hide); } } /** * cssHide的反向使用 * @param ele */ cssShow(ele: Element | null) { if (ele == null) { return; } popsDOMUtils.removeClassName(ele, PopsCommonCSSClassName.hide); popsDOMUtils.removeClassName(ele, PopsCommonCSSClassName.hideImportant); } /** * 将字符串转为Element元素 * @param html * @param useParser 是否使用DOMParser来生成元素,有些时候通过DOMParser生成的元素有点问题 * + true 使用DOMPraser来转换字符串 * + false (默认)创建一个div,里面放入字符串,然后提取firstChild * @param isComplete 是否是完整的 * + true 如果useParser为true,那么返回整个使用DOMParser转换成的Document * 如果useParser为false,返回一个DIV元素,DIV元素内包裹着需要转换的字符串 * + false (默认)如果useParser为true,那么返回整个使用DOMParser转换成的Document的body * 如果useParser为false,返回一个DIV元素的firstChild * @example * // 将字符串转为Element元素 * DOMUtils.parseHTML("") * > * @example * // 使用DOMParser将字符串转为Element元素 * DOMUtils.parseHTML("",true) * > * @example * // 由于需要转换的元素是多个元素,将字符串转为完整的Element元素 * DOMUtils.parseHTML("",false, true) * >
* @example * // 由于需要转换的元素是多个元素,使用DOMParser将字符串转为完整的Element元素 * DOMUtils.parseHTML("",true, true) * > #document */ toElement( html: string, useParser?: T1, isComplete?: T2 ): ParseHTMLReturnType; toElement(html: string, useParser = false, isComplete = false) { 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 $temp = PopsCore.document.createElement("div"); PopsSafeUtils.setSafeHTML($temp, html); if (isComplete) { return $temp; } else { return $temp.firstChild; } } if (useParser) { return parseHTMLByDOMParser(); } else { return parseHTMLByCreateDom(); } } /** * 函数在元素内部末尾添加子元素或HTML字符串 * @param element 目标元素 * @param content 子元素或HTML字符串 * @example * // 元素a.xx的内部末尾添加一个元素 * DOMUtils.append(document.querySelector("a.xx"), document.querySelector("b.xx")) * DOMUtils.append("a.xx","'") * */ append( element: Element | Node | ShadowRoot | HTMLElement | string, content: HTMLElement | string | (HTMLElement | string | Element)[] | NodeList ) { if (typeof element === "string") { element = this.selector(element) as HTMLElement; } if (element == null) { return; } function elementAppendChild(ele: HTMLElement, text: HTMLElement | string) { if (typeof content === "string") { ele.insertAdjacentHTML("beforeend", PopsSafeUtils.getSafeHTML(text as string)); } else { ele.appendChild(text as HTMLElement); } } if (Array.isArray(content) || content instanceof NodeList) { // 数组 const fragment = PopsCore.document.createDocumentFragment(); content.forEach((ele) => { if (typeof ele === "string") { ele = this.toElement(ele, true, false); } fragment.appendChild(ele); }); element.appendChild(fragment); } else { elementAppendChild(element as HTMLElement, content); } } /** * 把元素标签添加到head内 */ appendHead($ele: HTMLElement) { if (PopsCore.document.head) { PopsCore.document.head.appendChild($ele); } else { PopsCore.document.documentElement.appendChild($ele); } } /** * 把元素添加进body内 * @param $ele */ appendBody($ele: HTMLElement) { if (PopsCore.document.body) { PopsCore.document.body.appendChild($ele); } else { PopsCore.document.documentElement.appendChild($ele); } } /** * 判断元素是否已显示或已连接 * @param element */ isShow(element: HTMLElement) { return Boolean(element.getClientRects().length); } /** * 用于显示元素并获取它的高度宽度等其它属性 * @param $ele * @param parent 父元素 */ showElement($ele: HTMLElement, ownParent?: Node) { /** 克隆元素 */ const $cloneNode = $ele.cloneNode(true) as HTMLElement; $cloneNode.setAttribute("style", "visibility: hidden !important;display:block !important;"); let $parent: Node = PopsCore.document.documentElement; // 这里需要的是先获取元素的父节点,把元素同样添加到父节点中 const $root = $ele.getRootNode(); if (ownParent == null) { if ($root == $ele) { // 未添加到任意节点中,那么直接添加到页面中去 $parent = PopsCore.document.documentElement; } else { // 添加到父节点中 $parent = $root; } } else { // 自定义的父节点 $parent = ownParent; } $parent.appendChild($cloneNode); return { /** * 强制显示的克隆的元素 */ cloneNode: $cloneNode, /** * 恢复修改的style */ recovery() { popsDOMUtils.remove($cloneNode); }, }; } /** * 获取元素上的Float格式的属性px * @param element * @param styleName style名 */ getStyleValue(element: HTMLElement | CSSStyleDeclaration, styleName: string) { 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 as any]); if (isNaN(value)) { return 0; } else { return value; } } /** * 在元素前面添加兄弟元素或HTML字符串 * @param element 目标元素 * @param content 兄弟元素或HTML字符串 * @example * // 元素a.xx前面添加一个元素 * DOMUtils.before(document.querySelector("a.xx"),document.querySelector("b.xx")) * DOMUtils.before("a.xx","'") * */ before(element: HTMLElement | Element | string, content: HTMLElement | string) { if (typeof element === "string") { element = this.selector(element) as HTMLElement; } if (element == null) { return; } if (typeof content === "string") { element.insertAdjacentHTML("beforebegin", PopsSafeUtils.getSafeHTML(content)); } else { element!.parentElement!.insertBefore(content, element); } } /** * 在元素后面添加兄弟元素或HTML字符串 * @param element 目标元素 * @param content 兄弟元素或HTML字符串 * @example * // 元素a.xx后面添加一个元素 * DOMUtils.after(document.querySelector("a.xx"),document.querySelector("b.xx")) * DOMUtils.after("a.xx","'") * */ after(element: HTMLElement | Element | string, content: HTMLElement | string) { if (typeof element === "string") { element = this.selector(element) as HTMLElement; } if (element == null) { return; } if (typeof content === "string") { element.insertAdjacentHTML("afterend", PopsSafeUtils.getSafeHTML(content)); } else { element!.parentElement!.insertBefore(content, element.nextSibling); } } /** * 获取CSS Rule * @param sheet * @returns */ getKeyFrames(sheet: CSSStyleSheet) { const result = {}; Object.keys(sheet.cssRules).forEach((key) => { if ((sheet.cssRules as any)[key].type === 7 && (sheet.cssRules as any)[key].name.startsWith("pops-anim-")) { (result as any)[(sheet.cssRules as any)[key].name] = (sheet.cssRules as any)[key]; } }); return result; } /** * 颜色转换函数 * @method hexToRgb hex 颜色转 rgb 颜色 * @method rgbToHex rgb 颜色转 Hex 颜色 * @method getDarkColor 加深颜色值 * @method getLightColor 变浅颜色值 */ calcColor() { function useChangeColor() { /** * hex 颜色转 rgb 颜色 */ const hexToRgb = ( /** * hex 颜色值 */ str: string ): any => { let hexs: any = ""; const reg = /^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/; if (!reg.test(str)) { console.warn("输入错误的hex"); return ""; } str = str.replace("#", ""); hexs = str.match(/../g); for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16); return hexs; }; /** * rgb转hex */ const rgbToHex = ( /** * 红色 */ r: any, /** * 绿色 */ g: any, /** * 蓝色 */ b: any ): string => { const reg = /^\d{1,3}$/; if (!reg.test(r) || !reg.test(g) || !reg.test(b)) { console.warn("输入错误的rgb颜色值"); return ""; } const hexs = [r.toString(16), g.toString(16), b.toString(16)]; for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`; return `#${hexs.join("")}`; }; /** * 获取深色 */ const getDarkColor = ( /** * 颜色值字符串 */ color: string, /** * 加深的程度,限0-1之间 */ level: number ): string => { const reg = /^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/; if (!reg.test(color)) { console.warn("输入错误的hex颜色值"); return ""; } const rgb = useChangeColor().hexToRgb(color); for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level)); return useChangeColor().rgbToHex(rgb[0], rgb[1], rgb[2]); }; /** * 获取颜色变浅后的颜色值 */ const getLightColor = ( /** * 颜色值字符串 */ color: string, /** * 加深的程度,限0-1之间 */ level: number ): string => { const reg = /^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/; if (!reg.test(color)) { console.warn("输入错误的hex颜色值"); return ""; } const rgb = useChangeColor().hexToRgb(color); for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]); return useChangeColor().rgbToHex(rgb[0], rgb[1], rgb[2]); }; return { hexToRgb, rgbToHex, getDarkColor, getLightColor, }; } return useChangeColor(); } /** * 获取移动元素的transform偏移 * @param element 元素 */ getTransform(element: HTMLElement) { let transform_left = 0; let transform_top = 0; const elementTransform = PopsCore.globalThis.getComputedStyle(element).transform; if (elementTransform !== "none" && elementTransform != null && elementTransform !== "") { const elementTransformMatch = elementTransform.match(/\((.+)\)/); // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain const elementTransformSplit = elementTransformMatch?.[1]?.split?.(",")!; transform_left = Math.abs(parseInt(elementTransformSplit[4])); transform_top = Math.abs(parseInt(elementTransformSplit[5])); } return { transformLeft: transform_left, transformTop: transform_top, }; } /** * 移除元素(包括它和内部使用.on添加的监听事件) * @param $el 目标元素,可以是数组、单个元素、NodeList、元素选择器 * @example * DOMUtils.remove(document.querySelector("a.xx")) * DOMUtils.remove(document.querySelectorAll("a.xx")) * DOMUtils.remove("a.xx") * DOMUtils.remove([a.xxx, div.xxx, span.xxx]) * */ remove($el: PopsDOMUtilsTargetElementType | Element | null | undefined) { if (typeof $el === "string") { $el = this.selectorAll($el); } if ($el == null) { return; } if (CommonUtils.isNodeList($el)) { $el.forEach(($elItem) => { this.remove($elItem as HTMLElement); }); return; } // 移除事件 $el.querySelectorAll("*").forEach(($elItem) => { if (!($elItem instanceof Element)) return; this.offAll($elItem); }); this.offAll($el); if (typeof $el.remove === "function") { $el.remove(); } else if ($el.parentElement) { $el.parentElement.removeChild($el); } else if ($el.parentNode) { $el.parentNode.removeChild($el); } } } const popsDOMUtils = new PopsDOMUtils(); export { popsDOMUtils };