useContentHeight.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { ComputedRef, isRef, nextTick, Ref, ref, unref, watch } from 'vue';
  2. import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
  3. import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
  4. import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
  5. import { getViewportOffset } from '/@/utils/domUtils';
  6. import { isNumber, isString } from '/@/utils/is';
  7. export interface CompensationHeight {
  8. // 使用 layout Footer 高度作为判断补偿高度的条件
  9. useLayoutFooter: boolean;
  10. // refs HTMLElement
  11. elements?: Ref[];
  12. }
  13. type Upward = number | string | null | undefined;
  14. /**
  15. * 动态计算内容高度,根据锚点dom最下坐标到屏幕最下坐标,根据传入dom的高度、padding、margin等值进行动态计算
  16. * 最终获取合适的内容高度
  17. *
  18. * @param flag 用于开启计算的响应式标识
  19. * @param anchorRef 锚点组件 Ref<ElRef | ComponentRef>
  20. * @param subtractHeightRefs 待减去高度的组件列表 Ref<ElRef | ComponentRef>
  21. * @param substractSpaceRefs 待减去空闲空间(margins/paddings)的组件列表 Ref<ElRef | ComponentRef>
  22. * @param offsetHeightRef 计算偏移的响应式高度,计算高度时将直接减去此值
  23. * @param upwardSpace 向上递归减去空闲空间的 层级 或 直到指定class为止 数值为2代表向上递归两次|数值为ant-layout表示向上递归直到碰见.ant-layout为止
  24. * @returns 响应式高度
  25. */
  26. export function useContentHeight(
  27. flag: ComputedRef<Boolean>,
  28. anchorRef: Ref,
  29. subtractHeightRefs: Ref[],
  30. substractSpaceRefs: Ref[],
  31. upwardSpace: Ref<Upward> | ComputedRef<Upward> | Upward = 0,
  32. offsetHeightRef: Ref<number> = ref(0),
  33. ) {
  34. const contentHeight: Ref<Nullable<number>> = ref(null);
  35. const { footerHeightRef: layoutFooterHeightRef } = useLayoutHeight();
  36. let compensationHeight: CompensationHeight = {
  37. useLayoutFooter: true,
  38. };
  39. const setCompensation = (params: CompensationHeight) => {
  40. compensationHeight = params;
  41. };
  42. function redoHeight() {
  43. nextTick(() => {
  44. calcContentHeight();
  45. });
  46. }
  47. function calcSubtractSpace(
  48. element: Element | null | undefined,
  49. direction: 'all' | 'top' | 'bottom' = 'all',
  50. ): number {
  51. function numberPx(px: string) {
  52. return Number(px.replace(/[^\d]/g, ''));
  53. }
  54. let subtractHeight = 0;
  55. const ZERO_PX = '0px';
  56. if (element) {
  57. const cssStyle = getComputedStyle(element);
  58. const marginTop = numberPx(cssStyle?.marginTop ?? ZERO_PX);
  59. const marginBottom = numberPx(cssStyle?.marginBottom ?? ZERO_PX);
  60. const paddingTop = numberPx(cssStyle?.paddingTop ?? ZERO_PX);
  61. const paddingBottom = numberPx(cssStyle?.paddingBottom ?? ZERO_PX);
  62. if (direction === 'all') {
  63. subtractHeight += marginTop;
  64. subtractHeight += marginBottom;
  65. subtractHeight += paddingTop;
  66. subtractHeight += paddingBottom;
  67. } else if (direction === 'top') {
  68. subtractHeight += marginTop;
  69. subtractHeight += paddingTop;
  70. } else {
  71. subtractHeight += marginBottom;
  72. subtractHeight += paddingBottom;
  73. }
  74. }
  75. return subtractHeight;
  76. }
  77. function getEl(element: any): Nullable<HTMLDivElement> {
  78. if (element == null) {
  79. return null;
  80. }
  81. return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement;
  82. }
  83. async function calcContentHeight() {
  84. if (!flag.value) {
  85. return;
  86. }
  87. // Add a delay to get the correct height
  88. await nextTick();
  89. const anchorEl = getEl(unref(anchorRef));
  90. if (!anchorEl) {
  91. return;
  92. }
  93. const { bottomIncludeBody } = getViewportOffset(anchorEl);
  94. // substract elements height
  95. let substractHeight = 0;
  96. subtractHeightRefs.forEach((item) => {
  97. substractHeight += getEl(unref(item))?.offsetHeight ?? 0;
  98. });
  99. // subtract margins / paddings
  100. let substractSpaceHeight = calcSubtractSpace(anchorEl) ?? 0;
  101. substractSpaceRefs.forEach((item) => {
  102. substractSpaceHeight += calcSubtractSpace(getEl(unref(item)));
  103. });
  104. // upwardSpace
  105. let upwardSpaceHeight = 0;
  106. function upward(element: Element | null, upwardLvlOrClass: number | string | null | undefined) {
  107. if (element && upwardLvlOrClass) {
  108. const parent = element.parentElement;
  109. if (parent) {
  110. if (isString(upwardLvlOrClass)) {
  111. if (!parent.classList.contains(upwardLvlOrClass)) {
  112. upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
  113. upward(parent, upwardLvlOrClass);
  114. } else {
  115. upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
  116. }
  117. } else if (isNumber(upwardLvlOrClass)) {
  118. if (upwardLvlOrClass > 0) {
  119. upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
  120. upward(parent, --upwardLvlOrClass);
  121. }
  122. }
  123. }
  124. }
  125. }
  126. if (isRef(upwardSpace)) {
  127. upward(anchorEl, unref(upwardSpace));
  128. } else {
  129. upward(anchorEl, upwardSpace);
  130. }
  131. let height =
  132. bottomIncludeBody -
  133. unref(layoutFooterHeightRef) -
  134. unref(offsetHeightRef) -
  135. substractHeight -
  136. substractSpaceHeight -
  137. upwardSpaceHeight;
  138. // compensation height
  139. const calcCompensationHeight = () => {
  140. compensationHeight.elements?.forEach((item) => {
  141. height += getEl(unref(item))?.offsetHeight ?? 0;
  142. });
  143. };
  144. if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) {
  145. calcCompensationHeight();
  146. } else {
  147. calcCompensationHeight();
  148. }
  149. contentHeight.value = height;
  150. }
  151. onMountedOrActivated(() => {
  152. nextTick(() => {
  153. calcContentHeight();
  154. });
  155. });
  156. useWindowSizeFn(
  157. () => {
  158. calcContentHeight();
  159. },
  160. 50,
  161. { immediate: true },
  162. );
  163. watch(
  164. () => [layoutFooterHeightRef.value],
  165. () => {
  166. calcContentHeight();
  167. },
  168. {
  169. flush: 'post',
  170. immediate: true,
  171. },
  172. );
  173. return { redoHeight, setCompensation, contentHeight };
  174. }