dom-utils.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import clx from 'clsx';
  6. import { each } from './objects';
  7. import { Disposable } from './disposable';
  8. import { Cache, type CacheManager } from './cache';
  9. const toStyleKey = (key: string) => key.replace(/([A-Z])/, (k) => `-${k.toLowerCase()}`);
  10. export type CSSStyle = {
  11. [P in keyof CSSStyleDeclaration]?: string | number | undefined;
  12. };
  13. export interface DOMCache extends HTMLElement, Disposable {
  14. setStyle(style: CSSStyle): void;
  15. key?: string | number;
  16. }
  17. export namespace domUtils {
  18. export function toPixel(num: number): string {
  19. return `${num}px`;
  20. }
  21. // export function fromPixel(pixel: string): number {
  22. // return parseInt(pixel.substring(0, pixel.length - 2));
  23. // }
  24. export function fromPercent(percent: string): number {
  25. return parseFloat(percent.substring(0, percent.length - 1));
  26. }
  27. export function toPercent(percent: number): string {
  28. return `${percent}%`;
  29. }
  30. export function enableEvent(element: HTMLDivElement): void {
  31. element.style.pointerEvents = 'all';
  32. }
  33. export function disableEvent(element: HTMLDivElement): void {
  34. element.style.pointerEvents = 'none';
  35. }
  36. export function createElement<T extends HTMLElement>(ele: string, ...classNames: string[]): T {
  37. const element = document.createElement(ele);
  38. if (classNames.length > 0) {
  39. element.className = clx(classNames);
  40. }
  41. return element as T;
  42. }
  43. export function createDivWithClass(...classNames: string[]): HTMLDivElement {
  44. return createElement('div', ...classNames) as HTMLDivElement;
  45. }
  46. export function addClass(element: Element, ...classNames: string[]): void {
  47. element.className = clx(classNames.concat(element.className.split(' ')));
  48. }
  49. export function delClass(element: Element, ...classNames: string[]): void {
  50. classNames.forEach((name) => {
  51. element.classList.remove(name);
  52. });
  53. element.className = element.classList.toString();
  54. }
  55. export function coverClass(element: Element, ...classNames: string[]): void {
  56. element.className = clx(classNames);
  57. }
  58. export function clearChildren(container: HTMLDivElement): void {
  59. container.innerHTML = '';
  60. }
  61. export function translatePercent(node: HTMLDivElement, x: number, y: number): void {
  62. node.style.transform = `translate(${x}%, ${y}%)`;
  63. }
  64. export function translateXPercent(node: HTMLDivElement, x: number): void {
  65. node.style.transform = `translateX(${x}%)`;
  66. }
  67. export function translateYPercent(node: HTMLDivElement, y: number): void {
  68. node.style.transform = `translateY(${y}%)`;
  69. }
  70. export function setStyle(node: HTMLElement, styles: CSSStyle): void {
  71. const styleStrs: string[] = [];
  72. each(styles, (value, key) => {
  73. if (value === undefined) return;
  74. if (typeof value === 'number' && key !== 'opacity' && key !== 'zIndex' && key !== 'scale') {
  75. value = toPixel(value);
  76. }
  77. styleStrs.push(`${toStyleKey(key)}:${value}`);
  78. });
  79. const oldStyle = node.getAttribute('style');
  80. const newStyle = styleStrs.join(';');
  81. if (oldStyle !== newStyle) {
  82. node.setAttribute('style', newStyle);
  83. }
  84. }
  85. export function classNameWithPrefix(prefix: string): (key: string, opts?: any) => string {
  86. return (key: string, opts?: any) =>
  87. clx(
  88. key
  89. .split(/\s+/)
  90. .map((s) => `${prefix}-${s}`)
  91. .join(' '),
  92. opts
  93. );
  94. }
  95. export function addStandardDisposableListener(
  96. dom: HTMLElement | HTMLDocument,
  97. type: string,
  98. listener: EventListenerOrEventListenerObject | any,
  99. options?: boolean | any
  100. ): Disposable {
  101. dom.addEventListener(type, listener, options);
  102. return Disposable.create(() => {
  103. dom.removeEventListener(type, listener);
  104. });
  105. }
  106. /**
  107. * dom 缓存
  108. * @param parent
  109. * @param className
  110. */
  111. export function createDOMCache<T extends DOMCache = DOMCache>(
  112. parent: HTMLElement,
  113. className: string | (() => HTMLElement),
  114. children?: string
  115. ): CacheManager<T> {
  116. return Cache.create<T>((/* item */) => {
  117. // item 悬空了?
  118. const dom =
  119. typeof className === 'string' ? domUtils.createDivWithClass(className) : className();
  120. if (children) {
  121. dom.innerHTML = children;
  122. }
  123. parent.appendChild(dom);
  124. return Object.assign(dom, {
  125. // key: item ? item.key : undefined,
  126. dispose: () => {
  127. const { parentNode } = dom;
  128. if (parentNode) {
  129. parentNode.removeChild(dom);
  130. }
  131. },
  132. setStyle: (style: CSSStyle) => {
  133. domUtils.setStyle(dom, style);
  134. },
  135. }) as T;
  136. });
  137. }
  138. }