import { version } from "../package.json"; import { CommonUtils } from "./CommonUtils"; import { ElementHandler } from "./ElementHandler"; import { type DOMUtilsCSSProperty, type DOMUtilsCSSPropertyType } from "./types/DOMUtilsCSSProperty"; import type { DOMUtilsCreateElementAttributesMap } from "./types/DOMUtilsEvent"; import type { DOMUtilsTargetElementType } from "./types/global"; import type { WindowApiOption } from "./types/WindowApi"; class DOMUtils extends ElementHandler { constructor(option?: WindowApiOption) { 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; } /** * 获取元素的属性值 * @param $el 目标元素 * @param attrName 属性名 * @example * // 获取a.xx元素的href属性 * DOMUtils.attr(document.querySelector("a.xx"),"href"); * DOMUtils.attr("a.xx","href"); * > https://xxxx.... */ attr($el: DOMUtilsTargetElementType | Element, attrName: string): string; /** * 设置元素的属性值 * @param $el 目标元素 * @param attrName 属性名 * @param attrValue 属性值 * @example * // 修改a.xx元素的href属性为abcd * DOMUtils.attr(document.querySelector("a.xx"),"href","abcd"); * DOMUtils.attr("a.xx","href","abcd"); */ attr($el: DOMUtilsTargetElementType | Element, attrName: string, attrValue: string | boolean | number): void; attr($el: DOMUtilsTargetElementType | Element, attrName: string, attrValue?: any) { 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] as HTMLElement, attrName, attrValue); } else { // 设置属性 $el.forEach(($elItem) => { that.attr($elItem as HTMLElement, attrName, attrValue); }); return; } } if (attrValue == null) { return $el.getAttribute(attrName); } else { $el.setAttribute(attrName, attrValue); } } /** * 创建元素 * @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]; } & { [key: string]: any; }) | string, /** 自定义属性 */ attributes?: DOMUtilsCreateElementAttributesMap ): HTMLElementTagNameMap[K]; /** * 创建元素 * @param tagName 自定义的标签名 * @param property 属性 * @param attributes 元素上的自定义属性 * @example * // 创建一个custom-div元素,且属性class为xxx * DOMUtils.createElement("custom-div",undefined,{ class:"xxx" }); * > * @example * // 创建一个custom-div元素 * DOMUtils.createElement("custom-div"); * > * @example * // 创建一个custom-div元素 * DOMUtils.createElement("custom-div","测试"); * > 测试 */ createElement( /** 元素名 */ tagName: string, /** 属性 */ property?: | ({ [P in keyof HTMLElement]?: HTMLElement[P]; } & { [key: string]: any; }) | string, /** 自定义属性 */ attributes?: DOMUtilsCreateElementAttributesMap ): HTMLElement; createElement( /** 元素名 */ tagName: K, /** 属性 */ property?: | ({ [P in keyof HTMLElementTagNameMap[K]]?: HTMLElementTagNameMap[K][P]; } & { [key: string]: any; }) | string, /** 自定义属性 */ attributes?: DOMUtilsCreateElementAttributesMap ): HTMLElementTagNameMap[K] { 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; } /** * 获取元素的样式属性值 * @param $el 目标元素 * @param property 样式属性名或包含多个属性名和属性值的对象 * @example * // 获取元素a.xx的CSS属性display * DOMUtils.css(document.querySelector("a.xx"),"display"); * DOMUtils.css("a.xx","display"); * > "none" * */ css($el: DOMUtilsTargetElementType, property: DOMUtilsCSSPropertyType): 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: DOMUtilsTargetElementType, 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: DOMUtilsTargetElementType, property: DOMUtilsCSSPropertyType & 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: DOMUtilsTargetElementType, property: | DOMUtilsCSSProperty | { [key: string]: string | number; } | string ): string; css( $el: DOMUtilsTargetElementType, property: DOMUtilsCSSPropertyType | string | DOMUtilsCSSProperty, 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 (CommonUtils.isNodeList($el)) { 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 DOMUtilsCSSProperty); }); 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 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 as keyof typeof property]; setStyleProperty(prop, value!); } } else { // 其他情况 throw new TypeError("property must be string or object"); } } /** * 获取元素的文本内容,优先返回textContent * @param $el 目标元素 * @returns 如果传入了text,则返回undefined;否则返回文本内容 * @example * // 设置元素a.xx的文本内容为abcd * DOMUtils.text(document.querySelector("a.xx"),"abcd") * DOMUtils.text("a.xx","abcd") * DOMUtils.text("a.xx",document.querySelector("b")) * */ text($el: DOMUtilsTargetElementType | Element | DocumentFragment | Node): string; /** * 设置元素的文本内容 * @param $el 目标元素 * @param text (可选)文本内容 * @returns 如果传入了text,则返回undefined;否则返回文本内容 * @example * // 设置元素a.xx的文本内容为abcd * DOMUtils.text(document.querySelector("a.xx"),"abcd") * DOMUtils.text("a.xx","abcd") * DOMUtils.text("a.xx",document.querySelector("b")) * */ text( $el: DOMUtilsTargetElementType | Element | DocumentFragment | Node, text: string | HTMLElement | Element | number ): void; text($el: DOMUtilsTargetElementType | Element | DocumentFragment | Node, text?: any) { 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] as HTMLElement); } else { // 设置 $el.forEach(($elItem) => { that.text($elItem as HTMLElement, text); }); } return; } if (text == null) { return $el.textContent || ($el).innerText; } else { if (text instanceof Node) { text = text.textContent || (text as HTMLElement).innerText; } if ("textContent" in $el) { $el.textContent = text as string; } else if ("innerText" in $el) { ($el as HTMLElement).innerText = text as string; } } } /** * 设置元素的HTML内容 * @param element 目标元素 * @param html (可选)HTML内容|元素 * @returns 如果传入了html,则返回undefined;否则返回HTML内容 * @example * // 设置元素a.xx的文本内容为abcd * DOMUtils.html(document.querySelector("a.xx"),"abcd") * DOMUtils.html("a.xx","abcd") * DOMUtils.html("a.xx",document.querySelector("b")) * */ html(element: DOMUtilsTargetElementType, html: string | HTMLElement | Element | number): void; /** * 获取元素的HTML内容 * @param $el 目标元素 * @param html (可选)HTML内容|元素 * @returns 如果传入了html,则返回undefined;否则返回HTML内容 * @example * // 设置元素a.xx的文本内容为abcd * DOMUtils.html(document.querySelector("a.xx"),"abcd") * DOMUtils.html("a.xx","abcd") * DOMUtils.html("a.xx",document.querySelector("b")) * */ html($el: DOMUtilsTargetElementType): string; html($el: DOMUtilsTargetElementType, html?: any) { 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] as HTMLElement); } else { // 设置 $el.forEach(($elItem) => { that.html($elItem as HTMLElement, 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: HTMLElement, isShow: boolean = false ): { transformLeft: number; transformTop: number; } { 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, }; } /** * 设置元素的value属性值 * @param $el 目标元素 * @param value (可选)value属性值 * @returns 如果传入了value,则返回undefined;否则返回value属性值 * > true * @example * // 修改元素input.xx的复选框值为true * DOMUtils.val(document.querySelector("input.xx"),true) * DOMUtils.val("input.xx",true) * */ val( $el: | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | string | (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)[] | NodeListOf, value: string | boolean | number ): void; /** * 获取value属性值 * @param $el 目标元素 * @example * // 获取元素textarea的值 * DOMUtils.val(document.querySelector("textarea.xx")) * */ val( $el: | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | string | (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)[] | NodeListOf ): string; /** * 获取value属性值 * @param $el 目标元素 * @example * // 获取元素input.xx的复选框值 * DOMUtils.val(document.querySelector("input.xx")) * DOMUtils.val("input.xx") * */ val( $el: | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)[] | NodeListOf ): boolean | string; val( $el: | HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | string | (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)[] | NodeListOf, value?: string | boolean | number ) { 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] as HTMLInputElement); } else { // 设置 $el.forEach(($elItem) => { that.val($elItem as HTMLInputElement, value); }); } return; } if (value == null) { // 获取 if ($el.localName === "input" && ($el.type === "checkbox" || $el.type === "radio")) { return ($el as HTMLInputElement).checked; } else { return $el.value; } } else { // 设置 if ($el.localName === "input" && ($el.type === "checkbox" || $el.type === "radio")) { ($el as HTMLInputElement).checked = !!value; } else { $el.value = value as string; } } } /** * 获取元素的属性值 * @param $el 目标元素 * @param propName 属性名 * @param propValue 属性值 * @example * // 获取元素a.xx的属性data-value * DOMUtils.val(document.querySelector("a.xx"),"data-value") * DOMUtils.val("a.xx","data-value") * > undefined * */ prop($el: DOMUtilsTargetElementType | DocumentFragment, propName: string): T; /** * 设置元素的属性值 * @param $el 目标元素 * @param propName 属性名 * @param propValue 属性值 * @example * // 设置元素a.xx的属性data-value为1 * DOMUtils.val(document.querySelector("a.xx"),"data-value",1) * DOMUtils.val("a.xx","data-value",1) * */ prop($el: DOMUtilsTargetElementType | DocumentFragment, propName: string, propValue: T): void; prop($el: DOMUtilsTargetElementType | DocumentFragment, propName: string, propValue?: any) { 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] as HTMLElement, propName); } else { // 设置 $el.forEach(($elItem) => { that.prop($elItem as HTMLElement, 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: DOMUtilsTargetElementType | Element, attrName: string) { 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 as HTMLElement, 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: DOMUtilsTargetElementType | Element, className?: string | string[] | undefined | null) { 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 as HTMLElement, 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: DOMUtilsTargetElementType | DocumentFragment, propName: string) { 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 as HTMLElement, 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: DOMUtilsTargetElementType | Element, className: string | string[]) { 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 as HTMLElement, 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: DOMUtilsTargetElementType | Element, className: string | string[]): boolean { 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] as HTMLElement; 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 args 子元素或HTML字符串 * @example * // 元素a.xx的内部末尾添加一个元素 * DOMUtils.append(document.querySelector("a.xx"), document.querySelector("b.xx")) * DOMUtils.append("a.xx", "") * DOMUtils.append(document, [document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")]) * DOMUtils.append(document, document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")) * */ append( $el: DOMUtilsTargetElementType | DocumentFragment, ...args: ( | HTMLElement | string | Element | DocumentFragment | (HTMLElement | string | Element | DocumentFragment)[] | NodeList )[] ) { if (typeof $el === "string") { $el = this.selectorAll($el); } if ($el == null) { return; } if (CommonUtils.isNodeList($el)) { // 设置 $el.forEach(($elItem) => { this.append($elItem as HTMLElement, ...args); }); return; } const handler = ($ele: HTMLElement | DocumentFragment, $target: HTMLElement | string | Node) => { if ($ele instanceof DocumentFragment) { if (typeof $target === "string") { // 字符串转元素 $target = this.toElement($target, true, false); } $ele.appendChild($target); } else { if (typeof $target === "string") { $ele.insertAdjacentHTML("beforeend", CommonUtils.createSafeHTML($target)); } else { $ele.appendChild($target); } } }; const $fragment = this.windowApi.document.createDocumentFragment(); args.forEach((argItem) => { if (CommonUtils.isNodeList(argItem)) { // 数组 argItem.forEach((it) => { handler($fragment, it); }); } else { handler($fragment, argItem); } }); handler($el, $fragment); } /** * 函数 在元素内部开头添加子元素或HTML字符串 * @param $el 目标元素 * @param args 子元素或HTML字符串 * @example * // 元素a.xx内部开头添加一个元素 * DOMUtils.prepend(document.querySelector("a.xx"),document.querySelector("b.xx")) * DOMUtils.prepend("a.xx","'") * DOMUtils.prepend(document, [document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")]) * DOMUtils.prepend(document, document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")) * */ prepend( $el: DOMUtilsTargetElementType | DocumentFragment, ...args: ( | HTMLElement | string | Element | DocumentFragment | (HTMLElement | string | Element | DocumentFragment)[] | NodeList )[] ) { if (typeof $el === "string") { $el = this.selectorAll($el); } if ($el == null) { return; } if (CommonUtils.isNodeList($el)) { // 设置 $el.forEach(($elItem) => { this.prepend($elItem as HTMLElement, ...args); }); return; } const handler = ($ele: HTMLElement | DocumentFragment, $target: HTMLElement | string | Node) => { if ($ele instanceof DocumentFragment) { if (typeof $target === "string") { // 字符串转元素 $target = this.toElement($target, true, false); } $ele.appendChild($target); } else { if (typeof $target === "string") { $ele.insertAdjacentHTML("afterbegin", CommonUtils.createSafeHTML($target)); } else { const $firstChild = $ele.firstChild; if ($firstChild) { $ele.insertBefore($target, $firstChild); } else { $ele.prepend($target); } } } }; const $fragment = this.windowApi.document.createDocumentFragment(); args.forEach((argItem) => { if (CommonUtils.isNodeList(argItem)) { // 数组 argItem.forEach((it) => { handler($fragment, it); }); } else { handler($fragment, argItem); } }); handler($el, $fragment); } /** * 在元素后面添加兄弟元素或HTML字符串 * @param $el 目标元素 * @param args 兄弟元素或HTML字符串 * @example * // 元素a.xx后面添加一个元素 * DOMUtils.after(document.querySelector("a.xx"),document.querySelector("b.xx")) * DOMUtils.after("a.xx","'") * DOMUtils.after(document, [document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")]) * DOMUtils.after(document, document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")) * */ after( $el: DOMUtilsTargetElementType, ...args: ( | HTMLElement | string | Element | DocumentFragment | (HTMLElement | string | Element | DocumentFragment)[] | NodeList )[] ) { if (typeof $el === "string") { $el = this.selectorAll($el); } if ($el == null) { return; } if (CommonUtils.isNodeList($el)) { // 设置 $el.forEach(($elItem) => { this.after($elItem as HTMLElement, ...args); }); return; } const handler = ($ele: HTMLElement | DocumentFragment, $target: HTMLElement | string | Node) => { if ($ele instanceof DocumentFragment) { if (typeof $target === "string") { // 字符串转元素 $target = this.toElement($target, true, false); } $ele.appendChild($target); } else { if (typeof $target === "string") { $ele.insertAdjacentHTML("afterend", CommonUtils.createSafeHTML($target)); } else { const $parent = $el.parentElement; const $nextSlibling = $el.nextSibling; if ($parent && $nextSlibling) { $parent.insertBefore($target, $nextSlibling); } else { $el.after($target); } } } }; const $fragment = this.windowApi.document.createDocumentFragment(); args.forEach((argItem) => { if (CommonUtils.isNodeList(argItem)) { // 数组 argItem.forEach((it) => { handler($fragment, it); }); } else { handler($fragment, argItem); } }); handler($el, $fragment); } /** * 在元素前面添加兄弟元素或HTML字符串 * @param $el 目标元素 * @param args 兄弟元素或HTML字符串 * @example * // 元素a.xx前面添加一个元素 * DOMUtils.before(document.querySelector("a.xx"),document.querySelector("b.xx")) * DOMUtils.before("a.xx","'") * DOMUtils.before(document, [document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")]) * DOMUtils.before(document, document.querySelector("b.xx"), document.querySelector("c.xx"), document.querySelector("d.xx")) * * */ before( $el: DOMUtilsTargetElementType, ...args: ( | HTMLElement | string | Element | DocumentFragment | (HTMLElement | string | Element | DocumentFragment)[] | NodeList )[] ) { if (typeof $el === "string") { $el = this.selectorAll($el); } if ($el == null) { return; } if (CommonUtils.isNodeList($el)) { // 设置 $el.forEach(($elItem) => { this.before($elItem as HTMLElement, ...args); }); return; } const handler = ($ele: HTMLElement | DocumentFragment, $target: HTMLElement | string | Node) => { if ($ele instanceof DocumentFragment) { if (typeof $target === "string") { // 字符串转元素 $target = this.toElement($target, true, false); } $ele.appendChild($target); } else { if (typeof $target === "string") { $el.insertAdjacentHTML("beforebegin", CommonUtils.createSafeHTML($target)); } else { const $parent = $el.parentElement; if ($parent) { $parent.insertBefore($target, $el); } else { $el.before($target); } } } }; const $fragment = this.windowApi.document.createDocumentFragment(); args.forEach((argItem) => { if (CommonUtils.isNodeList(argItem)) { // 数组 argItem.forEach((it) => { handler($fragment, it); }); } else { handler($fragment, argItem); } }); handler($el, $fragment); } /** * 移除元素(包括它和内部使用.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: DOMUtilsTargetElementType | 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); } } /** * 移除元素内所有的子元素 * @param $el 目标元素 * @example * // 移除元素a.xx元素的所有子元素 * DOMUtils.empty(document.querySelector("a.xx")) * DOMUtils.empty("a.xx") * */ empty($el: DOMUtilsTargetElementType | Element) { 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 as HTMLElement); }); return; } if ($el.innerHTML) { CommonUtils.setSafeHTML($el, ""); } else if ($el.textContent) { $el.textContent = ""; } } /** * 获取元素相对于文档的偏移坐标(加上文档的滚动条) * @param $el 目标元素 * @example * // 获取元素a.xx的对于文档的偏移坐标 * DOMUtils.offset(document.querySelector("a.xx")) * DOMUtils.offset("a.xx") * > 0 */ offset($el: HTMLElement | string) { const that = this; if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } 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, }; } /** * 获取元素的宽度 * @param $el 要获取宽度的元素 * @param value 宽度值 * @param isShow 是否已进行isShow,避免爆堆栈 * @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($el: HTMLElement | string | Window | typeof globalThis | Document, isShow?: boolean): number; width($el: HTMLElement | string | Window | typeof globalThis | Document, isShow: boolean = false): number { const that = this; if (typeof $el === "string") { $el = that.selector($el)!; } if (CommonUtils.isWin($el)) { return that.windowApi.window.document.documentElement.clientWidth; } if (($el as HTMLElement).nodeType === 9) { /* Document文档节点 */ $el = $el as Document; return Math.max( $el.body.scrollWidth, $el.documentElement.scrollWidth, $el.body.offsetWidth, $el.documentElement.offsetWidth, $el.documentElement.clientWidth ); } if (isShow || (!isShow && CommonUtils.isShow($el as HTMLElement))) { /* 已显示 */ /* 不从style中获取对应的宽度,因为可能使用了class定义了width !important */ $el = $el as HTMLElement; /* 如果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 as HTMLElement; const { recovery } = CommonUtils.forceShow($el); const width = that.width($el, true); recovery(); return width; } } /** * 获取元素的高度 * @param $el 要获取高度的元素 * @param isShow 是否已进行isShow,避免爆堆栈 * @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($el: HTMLElement | string | Window | typeof globalThis | Document, isShow?: boolean): number; height($el: HTMLElement | string | Window | typeof globalThis | Document, isShow: boolean = false): number { const that = this; if (CommonUtils.isWin($el)) { return that.windowApi.window.document.documentElement.clientHeight; } if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } if (($el as Document).nodeType === 9) { $el = $el as Document; /* 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 as HTMLElement))) { $el = $el as HTMLElement; /* 已显示 */ /* 从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 as HTMLElement; const { recovery } = CommonUtils.forceShow($el); const height = that.height($el, true); recovery(); return height; } } /** * 获取元素的外部宽度(包括边框和外边距) * @param $el 要获取外部宽度的元素 * @param [isShow=false] 是否已进行isShow,避免爆堆栈 * @returns 元素的外部宽度,单位为像素 * @example * // 获取元素a.xx的外部宽度 * DOMUtils.outerWidth(document.querySelector("a.xx")) * DOMUtils.outerWidth("a.xx") * > 100 * // 获取window的外部宽度 * DOMUtils.outerWidth(window) * > 400 */ outerWidth($el: HTMLElement | string | Window | typeof globalThis | Document, isShow?: boolean): number; outerWidth($el: HTMLElement | string | Window | typeof globalThis | Document, isShow: boolean = false): number { const that = this; if (CommonUtils.isWin($el)) { return that.windowApi.window.innerWidth; } if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } $el = $el as HTMLElement; 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; } } /** * 获取元素的外部高度(包括边框和外边距) * @param {HTMLElement|string} $el 要获取外部高度的元素 * @param {boolean} [isShow=false] 是否已进行isShow,避免爆堆栈 * @returns {number} 元素的外部高度,单位为像素 * @example * // 获取元素a.xx的外部高度 * DOMUtils.outerHeight(document.querySelector("a.xx")) * DOMUtils.outerHeight("a.xx") * > 100 * // 获取window的外部高度 * DOMUtils.outerHeight(window) * > 700 */ outerHeight($el: HTMLElement | string | Window | typeof globalThis | Document, isShow?: boolean): number; outerHeight($el: HTMLElement | string | Window | typeof globalThis | Document, isShow: boolean = false): number { const that = this; if (CommonUtils.isWin($el)) { return that.windowApi.window.innerHeight; } if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } $el = $el as HTMLElement; 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",'') */ replaceWith($el: DOMUtilsTargetElementType, $newEl: HTMLElement | string | Node) { 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 as HTMLElement, $newEl); }); return; } if (typeof $newEl === "string") { $newEl = that.toElement($newEl, false, false); } const $parent = $el.parentElement; if ($parent) { $parent.replaceChild($newEl as Node, $el); } else { that.after($el, $newEl as HTMLElement); this.remove($el); } } /** * 将一个元素包裹在指定的HTML元素中 * @param $el 要包裹的元素 * @param wrapperHTML 要包裹的HTML元素的字符串表示形式 * @example * // 将a.xx元素外面包裹一层div * DOMUtils.wrap(document.querySelector("a.xx"),"
") */ wrap($el: DOMUtilsTargetElementType, wrapperHTML: string) { 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 as HTMLElement, wrapperHTML); }); return; } $el = $el as HTMLElement; // 创建一个新的div元素,并将wrapperHTML作为其innerHTML const $wrapper = that.windowApi.document.createElement("div"); that.html($wrapper, wrapperHTML); const wrapperFirstChild = $wrapper.firstChild as HTMLElement; // 将要包裹的元素插入目标元素前面 const parentElement = $el.parentElement as HTMLElement; parentElement.insertBefore(wrapperFirstChild, $el); // 将要包裹的元素移动到wrapper中 wrapperFirstChild.appendChild($el); } /** * 获取当前元素的前一个兄弟元素 * @param $el 当前元素 * @returns 前一个兄弟元素 * @example * // 获取a.xx元素前一个兄弟元素 * DOMUtils.prev(document.querySelector("a.xx")) * DOMUtils.prev("a.xx") * >
....
*/ prev($el: HTMLElement | string): HTMLElement; prev($el: HTMLElement | string) { const that = this; if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } if ($el == null) { return; } return $el.previousElementSibling as HTMLElement; } /** * 获取当前元素的后一个兄弟元素 * @param $el 当前元素 * @returns 后一个兄弟元素 * @example * // 获取a.xx元素前一个兄弟元素 * DOMUtils.next(document.querySelector("a.xx")) * DOMUtils.next("a.xx") * >
....
*/ next($el: HTMLElement | string): HTMLElement; next($el: HTMLElement | string) { const that = this; if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } if ($el == null) { return; } return $el.nextElementSibling as HTMLElement; } /** * 获取当前元素的所有兄弟元素 * @param element 当前元素 * @returns 所有兄弟元素 * @example * // 获取a.xx元素所有兄弟元素 * DOMUtils.siblings(document.querySelector("a.xx")) * DOMUtils.siblings("a.xx") * > (3)[div.logo-wrapper, div.forum-block, div.more-btn-desc] */ siblings(element: HTMLElement | string): HTMLElement[]; siblings($el: HTMLElement | string) { const that = this; if (typeof $el === "string") { $el = that.selector($el) as HTMLElement; } if ($el == null) { return; } return Array.from(($el.parentElement as HTMLElement).children as HTMLCollectionOf).filter( ($child) => $child !== $el ); } /** * 获取当前元素的父元素 * @param $el 当前元素 * @returns 父元素 * @example * // 获取a.xx元素的父元素 * DOMUtils.parent(document.querySelector("a.xx")) * DOMUtils.parent("a.xx") * >
....
*/ parent($el: HTMLElement | string): HTMLElement; /** * 获取当前元素的父元素 * @param $el 当前元素 * @returns 父元素 * @example * // 获取a.xx元素的父元素 * DOMUtils.parent(document.querySelector("a.xx")) * DOMUtils.parent("a.xx") * >
....
*/ parent($el: HTMLElement[] | NodeList): HTMLElement[]; /** * 获取当前元素的父元素 * @param $el 当前元素 * @returns 父元素 * @example * // 获取a.xx元素的父元素 * DOMUtils.parent(document.querySelector("a.xx")) * DOMUtils.parent("a.xx") * >
....
*/ parent($el: HTMLElement | Element | Node | NodeList | string | HTMLElement[]) { const that = this; if (typeof $el === "string") { $el = that.selector($el)!; } if ($el == null) { return; } if (CommonUtils.isNodeList($el)) { const resultArray: HTMLElement[] = []; $el.forEach(($elItem) => { resultArray.push(that.parent($elItem as HTMLElement)); }); return resultArray; } else { return $el.parentElement; } } /** * 将字符串转为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.toElement("") * > * @example * // 使用DOMParser将字符串转为Element元素 * DOMUtils.toElement("",true) * > * @example * // 由于需要转换的元素是多个元素,将字符串转为完整的Element元素 * DOMUtils.toElement("",false, true) * >
* @example * // 由于需要转换的元素是多个元素,使用DOMParser将字符串转为完整的Element元素 * DOMUtils.toElement("",true, true) * > #document */ toElement( html: string, useParser?: T1, isComplete?: T2 ): T1 extends true ? (T2 extends true ? Document : HTMLElement) : HTMLElement; toElement(html: string, 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(); } } /** * 将字符串转为Element元素数组 * @param html * @param useParser 是否使用DOMParser来生成元素,有些时候通过DOMParser生成的元素有点问题 * + true 使用DOMPraser来转换字符串 * + false (默认)创建一个div,里面放入字符串,然后提取childNodes * @example * // 将字符串转为Element元素数组 * DOMUtils.toElements("") * > [] * @example * // 使用DOMParser将字符串转为Element元素数组 * DOMUtils.toElements("",true) * > [] */ toElements(html: string, useParser = false) { const that = this; // 去除html前后的空格 html = html.trim(); function parseHTMLByDOMParser() { const parser = new DOMParser(); return Array.from(parser.parseFromString(html, "text/html").body.childNodes); } function parseHTMLByCreateDom() { const $el = that.windowApi.document.createElement("div"); that.html($el, html); return Array.from($el.childNodes); } if (useParser) { return parseHTMLByDOMParser(); } else { return parseHTMLByCreateDom(); } } /** * 序列化表单元素 * @param $form 表单元素 * @example * DOMUtils.serialize(document.querySelector("form")) * > xxx=xxx&aaa= */ serialize($form: HTMLFormElement): string { if (!($form instanceof HTMLFormElement)) { throw new TypeError("DOMUtils.serialize 参数必须是HTMLFormElement"); } const elements = $form.elements; const serializedArray: { name: string; value: string }[] = []; for (let index = 0; index < elements.length; index++) { const $el = elements[index] as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; if ( $el.name && !$el.disabled && (($el as HTMLInputElement).checked || ["text", "hidden", "password", "textarea", "select-one", "select-multiple"].includes($el.type)) ) { if ($el.type === "select-multiple") { for (let j = 0; j < ($el as HTMLSelectElement).options.length; j++) { if (($el as HTMLSelectElement).options[j].selected) { serializedArray.push({ name: ($el as HTMLSelectElement).name, value: ($el as HTMLSelectElement).options[j].value, }); } } } else { serializedArray.push({ name: $el.name, value: $el.value }); } } } return serializedArray .map((item) => `${encodeURIComponent(item.name)}=${encodeURIComponent(item.value)}`) .join("&"); } /** * 创建一个新的DOMUtils实例 * @param option * @returns */ createDOMUtils(option?: WindowApiOption) { return new DOMUtils(option); } /** * 获取文字的位置信息 * @param $input 输入框 * @param selectionStart 起始位置 * @param selectionEnd 结束位置 * @example * DOMUtils.getTextBoundingRect(document.querySelector("input")); */ getTextBoundingRect( $input: HTMLInputElement, selectionStart?: number | string, selectionEnd?: number | string ): DOMRect { 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 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(), 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) as number; topPos += getInputCSS("border-top-width", true) as number; leftPos += getInputCSS("padding-left", true) as number; leftPos += getInputCSS("border-left-width", true) as number; 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: number, end: number) { 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: string, isNumber: T): T extends true ? number : string { const val = that.windowApi.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; } } } /** * 在页面中增加style元素,如果html节点存在子节点,添加子节点第一个,反之,添加到html节点的子节点最后一个 * @param cssText css字符串 * @returns 返回添加的CSS标签 * @example * DOMUtils.addStyle("html{}"); * > */ addStyle(cssText: string): HTMLStyleElement; addStyle(cssText: string): HTMLStyleElement { 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: Element | Node | HTMLElement) { const that = this; if (!CommonUtils.isDOM($el)) { throw new Error("DOMUtils.checkUserClickInNode 参数 targetNode 必须为 Element|Node 类型"); } const clickEvent = that.windowApi.window.event as PointerEvent; const touchEvent = that.windowApi.window.event as TouchEvent; const $click = clickEvent?.composedPath()?.[0] as HTMLElement; // 点击的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; } } /** * 删除某个父元素,父元素可能在上层或上上层或上上上层... * @param $el 当前元素 * @param parentSelector 判断是否满足父元素,参数为当前处理的父元素,满足返回true,否则false * @returns * + true 已删除 * + false 未删除 * @example * DOMUtils.deleteParentNode(document.querySelector("a"),".xxx"); * > true **/ deleteParentNode($el: Node | HTMLElement | Element | null, parentSelector: string): boolean; deleteParentNode($el: Node | HTMLElement | Element | null, parentSelector: string) { 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 as HTMLElement, parentSelector); if ($parent) { this.remove($parent); result = true; } return result; } /** * 定位元素上的字符串,返回一个迭代器 * @param $el 目标元素 * @param text 需要定位的字符串 * @param filter (可选)过滤器函数,返回值为true是排除该元素 * @example * let textIterator = DOMUtils.findElementsWithText(document.documentElement,"xxxx"); * textIterator.next(); * > {value: ?HTMLElement, done: boolean, next: Function} */ findElementsWithText( $el: T, text: string, filter?: (element: T) => boolean ): Generator; *findElementsWithText( $el: T, text: string, filter?: (element: T) => boolean ) { 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 as any; } } 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] as T; yield* that.findElementsWithText($child, text, filter); } } /** * 寻找可见元素,如果元素不可见,则向上找它的父元素直至找到,如果父元素不存在则返回null * @param $el * @example * let visibleElement = DOMUtils.findVisibleElement(document.querySelector("a.xx")); * > */ findVisibleElement($el: HTMLElement | Element | Node) { let $current = $el as HTMLElement; while ($current) { const rect = $current.getBoundingClientRect(); if ((rect as any).length) { return $current; } $current = $current.parentElement as HTMLElement; } return null; } /** * 将元素上的文本或元素使用光标进行选中 * * 注意,如果设置startIndex和endIndex,且元素上并无可选则的坐标,那么会报错 * @param $el 目标元素 * @param childTextNode 目标元素下的#text元素 * @param startIndex (可选)开始坐标,可为空 * @param endIndex (可选)结束坐标,可为空 * @example * DOMUtils.setElementSelection(document.querySelector("span")); */ setElementSelection( $el: HTMLElement | Element | Node, childTextNode?: ChildNode, startIndex?: number, endIndex?: number ): void { 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(); export { domUtils as DOMUtils };