autofit.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. export interface IgnoreOption {
  2. el: string;
  3. height?: string;
  4. width?: string;
  5. scale?: number;
  6. fontSize?: number;
  7. }
  8. export interface AutofitOption {
  9. el?: string;
  10. dw?: number;
  11. dh?: number;
  12. resize?: boolean;
  13. ignore?: (IgnoreOption | string)[];
  14. transition?: number;
  15. delay?: number;
  16. limit?: number;
  17. cssMode?: "scale" | "zoom";
  18. allowScroll?: boolean;
  19. }
  20. declare interface Autofit {
  21. /**
  22. * 参数列表
  23. * 对象:
  24. *
  25. * @param {AutofitOption|String|undefined} options
  26. * @param {boolean|undefined} isShowInitTip
  27. * - 传入对象,对象中的属性如下:
  28. * - el(可选):渲染的元素,默认是 "body"
  29. * - dw(可选):设计稿的宽度,默认是 1920
  30. * - dh(可选):设计稿的高度,默认是 1080
  31. * - resize(可选):是否监听resize事件,默认是 true
  32. * - ignore(可选):忽略缩放的元素(该元素将反向缩放),参数见readme.md
  33. * - transition(可选):过渡时间,默认是 0
  34. * - delay(可选):延迟,默认是 0
  35. * - limit(可选):缩放限制,默认是 0.1
  36. * - cssMode(可选):缩放模式,默认是 scale,可选值有 scale 和 zoom, zoom 模式可能对事件偏移有利
  37. */
  38. init(options?: AutofitOption | string, isShowInitTip?: boolean): void;
  39. /**
  40. * @param {String} id
  41. * 关闭autofit.js造成的影响
  42. *
  43. */
  44. off(id?: string): void;
  45. /**
  46. * 检查autofit.js是否正在运行
  47. */
  48. isAutofitRunning: boolean;
  49. /**
  50. * @param {string} el - 待处理的元素选择器
  51. * @param {boolean} isKeepRatio - 是否保持纵横比(可选,默认为true,false时将充满父元素)
  52. * @param {number|undefined} level - 缩放等级,用于手动调整缩放程度(可选,默认为 1)
  53. */
  54. elRectification: typeof elRectification;
  55. /**
  56. * 当前缩放比例
  57. */
  58. scale: number
  59. }
  60. // type Ignore = Array<{ height: number, width: number, fontSize: number, scale: number, el: HTMLElement, dom: HTMLElement }>;
  61. let currRenderDom: string | HTMLElement = null!;
  62. let currelRectification = "";
  63. let currelRectificationLevel: string | number = "";
  64. let currelRectificationIsKeepRatio: string | boolean = "";
  65. let resizeListener: EventListenerOrEventListenerObject = null!;
  66. let timer: number = null!;
  67. let currScale: string | number = 1;
  68. let isElRectification = false;
  69. const autofit: Autofit = {
  70. isAutofitRunning: false,
  71. init(options = {}, isShowInitTip = true) {
  72. if (isShowInitTip) {
  73. console.log(`autofit.js is running`);
  74. }
  75. const {
  76. dw = 1920,
  77. dh = 1080,
  78. el = typeof options === "string" ? options : "body",
  79. resize = true,
  80. ignore = [],
  81. transition = "none",
  82. delay = 0,
  83. limit = 0.1,
  84. cssMode = "scale",
  85. allowScroll = false,
  86. } = options as AutofitOption;
  87. currRenderDom = el;
  88. const dom = document.querySelector<HTMLElement>(el);
  89. if (!dom) {
  90. console.error(`autofit: '${el}' is not exist`);
  91. return;
  92. }
  93. const style = document.createElement("style");
  94. const ignoreStyle = document.createElement("style");
  95. style.lang = "text/css";
  96. ignoreStyle.lang = "text/css";
  97. style.id = "autofit-style";
  98. ignoreStyle.id = "ignoreStyle";
  99. !allowScroll && (style.innerHTML = `body {overflow: hidden;}`);
  100. const bodyEl = document.querySelector("body")!;
  101. bodyEl.appendChild(style);
  102. bodyEl.appendChild(ignoreStyle);
  103. dom.style.height = `${dh}px`;
  104. dom.style.width = `${dw}px`;
  105. dom.style.transformOrigin = `0 0`;
  106. !allowScroll && (dom.style.overflow = "hidden");
  107. keepFit(dw, dh, dom, ignore, limit, cssMode);
  108. resizeListener = () => {
  109. clearTimeout(timer);
  110. if (delay != 0)
  111. timer = setTimeout(() => {
  112. keepFit(dw, dh, dom, ignore, limit, cssMode);
  113. isElRectification &&
  114. elRectification(currelRectification, currelRectificationIsKeepRatio, currelRectificationLevel);
  115. }, delay) as unknown as number;
  116. else {
  117. keepFit(dw, dh, dom, ignore, limit, cssMode);
  118. isElRectification &&
  119. elRectification(currelRectification, currelRectificationIsKeepRatio, currelRectificationLevel);
  120. }
  121. };
  122. resize && window.addEventListener("resize", resizeListener);
  123. this.isAutofitRunning = true;
  124. setTimeout(() => {
  125. dom.style.transition = `${transition}s`;
  126. });
  127. },
  128. off(el = "body") {
  129. try {
  130. window.removeEventListener("resize", resizeListener);
  131. const autofitStyle = document.querySelector("#autofit-style");
  132. autofitStyle && autofitStyle.remove();
  133. const ignoreStyleDOM = document.querySelector("#ignoreStyle");
  134. ignoreStyleDOM && ignoreStyleDOM.remove();
  135. const temp = document.querySelector<HTMLDivElement>(currRenderDom ? currRenderDom as string : el);
  136. temp && (temp.style.cssText = "")
  137. isElRectification && offelRectification();
  138. } catch (error) {
  139. console.error(`autofit: Failed to remove normally`, error);
  140. }
  141. this.isAutofitRunning = false;
  142. console.log(`autofit.js is off`);
  143. },
  144. elRectification: null!,
  145. scale: currScale
  146. };
  147. function elRectification(el: string, isKeepRatio: string | boolean = true, level: string | number = 1) {
  148. if (!autofit.isAutofitRunning) {
  149. console.error("autofit.js:(elRectification): autofit has not been initialized yet");
  150. return;
  151. }
  152. offelRectification();
  153. !el && console.error(`autofit.js:elRectification bad selector: ${el}`);
  154. currelRectification = el;
  155. currelRectificationLevel = level;
  156. currelRectificationIsKeepRatio = isKeepRatio;
  157. const currEl = Array.from(document.querySelectorAll<HTMLElement & { originalWidth: number, originalHeight: number }>(el));
  158. if (currEl.length == 0) {
  159. console.error(`autofit.js:elRectification found no element by selector: "${el}"`);
  160. return;
  161. }
  162. for (const item of currEl) {
  163. const rectification = currScale == 1 ? 1 : Number(currScale) * Number(level);
  164. if (!isElRectification) {
  165. item.originalWidth = item.clientWidth;
  166. item.originalHeight = item.clientHeight;
  167. }
  168. if (isKeepRatio) {
  169. item.style.width = `${item.originalWidth * rectification}px`;
  170. item.style.height = `${item.originalHeight * rectification}px`;
  171. } else {
  172. item.style.width = `${100 * rectification}%`;
  173. item.style.height = `${100 * rectification}%`;
  174. }
  175. item.style.transform = `translateZ(0) scale(${1 / Number(currScale)})`;
  176. item.style.transformOrigin = `0 0`;
  177. }
  178. isElRectification = true;
  179. }
  180. function offelRectification() {
  181. if (!currelRectification) return;
  182. isElRectification = false;
  183. for (const item of Array.from(document.querySelectorAll<HTMLElement>(currelRectification))) {
  184. item.style.width = ``;
  185. item.style.height = ``;
  186. item.style.transform = ``;
  187. }
  188. }
  189. function keepFit(
  190. dw: number,
  191. dh: number,
  192. dom: HTMLElement,
  193. ignore: AutofitOption['ignore'],
  194. limit: number,
  195. cssMode: AutofitOption['cssMode'] = "scale"
  196. ) {
  197. const clientHeight = document.documentElement.clientHeight;
  198. const clientWidth = document.documentElement.clientWidth;
  199. currScale =
  200. clientWidth / clientHeight < dw / dh ? clientWidth / dw : clientHeight / dh;
  201. currScale = Math.abs(1 - currScale) > limit ? currScale : 1;
  202. autofit.scale = +currScale;
  203. const height = Math.round(clientHeight / Number(currScale));
  204. const width = Math.round(clientWidth / Number(currScale));
  205. dom.style.height = `${height}px`;
  206. dom.style.width = `${width}px`;
  207. if (cssMode === "zoom") {
  208. (dom.style as any).zoom = `${currScale}`;
  209. } else {
  210. dom.style.transform = `translateZ(0) scale(${currScale})`;
  211. }
  212. const ignoreStyleDOM = document.querySelector("#ignoreStyle")!;
  213. ignoreStyleDOM.innerHTML = "";
  214. for (const temp of ignore!) {
  215. const item = temp as IgnoreOption & { dom: string };
  216. let itemEl = item.el || item.dom;
  217. typeof item == "string" && (itemEl = item);
  218. if (!itemEl || (typeof itemEl === "object" && !Object.keys(itemEl).length)) {
  219. console.error(`autofit: found invalid or empty selector/object: ${itemEl}`);
  220. continue;
  221. }
  222. const realScale: number = item.scale ? item.scale : 1 / Number(currScale);
  223. const realFontSize = realScale != currScale && item.fontSize;
  224. const realWidth = realScale != currScale && item.width;
  225. const realHeight = realScale != currScale && item.height;
  226. ignoreStyleDOM.innerHTML += `\n${itemEl} {
  227. transform: scale(${realScale})!important;
  228. transform-origin: 0 0;
  229. ${realWidth ? `width: ${realWidth}!important;` : ''}
  230. ${realHeight ? `height: ${realHeight}!important;` : ''}
  231. }`;
  232. if (realFontSize) {
  233. ignoreStyleDOM.innerHTML += `\n${itemEl} div ,${itemEl} span,${itemEl} a,${itemEl} * {
  234. font-size: ${realFontSize}px;
  235. }`;
  236. }
  237. }
  238. }
  239. autofit.elRectification = elRectification;
  240. export { autofit as default, elRectification };