useContentHeight.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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(element: Element | null | undefined, direction: 'all' | 'top' | 'bottom' = 'all'): number {
  48. function numberPx(px: string) {
  49. return Number(px.replace(/[^\d]/g, ''));
  50. }
  51. let subtractHeight = 0;
  52. const ZERO_PX = '0px';
  53. if (element) {
  54. const cssStyle = getComputedStyle(element);
  55. const marginTop = numberPx(cssStyle?.marginTop ?? ZERO_PX);
  56. const marginBottom = numberPx(cssStyle?.marginBottom ?? ZERO_PX);
  57. const paddingTop = numberPx(cssStyle?.paddingTop ?? ZERO_PX);
  58. const paddingBottom = numberPx(cssStyle?.paddingBottom ?? ZERO_PX);
  59. if (direction === 'all') {
  60. subtractHeight += marginTop;
  61. subtractHeight += marginBottom;
  62. subtractHeight += paddingTop;
  63. subtractHeight += paddingBottom;
  64. } else if (direction === 'top') {
  65. subtractHeight += marginTop;
  66. subtractHeight += paddingTop;
  67. } else {
  68. subtractHeight += marginBottom;
  69. subtractHeight += paddingBottom;
  70. }
  71. }
  72. return subtractHeight;
  73. }
  74. function getEl(element: any): Nullable<HTMLDivElement> {
  75. if (element == null) {
  76. return null;
  77. }
  78. return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement;
  79. }
  80. async function calcContentHeight() {
  81. if (!flag.value) {
  82. return;
  83. }
  84. // Add a delay to get the correct height
  85. await nextTick();
  86. const anchorEl = getEl(unref(anchorRef));
  87. if (!anchorEl) {
  88. return;
  89. }
  90. const { bottomIncludeBody } = getViewportOffset(anchorEl);
  91. // substract elements height
  92. let substractHeight = 0;
  93. subtractHeightRefs.forEach((item) => {
  94. substractHeight += getEl(unref(item))?.offsetHeight ?? 0;
  95. });
  96. // subtract margins / paddings
  97. let substractSpaceHeight = calcSubtractSpace(anchorEl) ?? 0;
  98. substractSpaceRefs.forEach((item) => {
  99. substractSpaceHeight += calcSubtractSpace(getEl(unref(item)));
  100. });
  101. // upwardSpace
  102. let upwardSpaceHeight = 0;
  103. function upward(element: Element | null, upwardLvlOrClass: number | string | null | undefined) {
  104. if (element && upwardLvlOrClass) {
  105. const parent = element.parentElement;
  106. if (parent) {
  107. if (isString(upwardLvlOrClass)) {
  108. if (!parent.classList.contains(upwardLvlOrClass)) {
  109. upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
  110. upward(parent, upwardLvlOrClass);
  111. } else {
  112. upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
  113. }
  114. } else if (isNumber(upwardLvlOrClass)) {
  115. if (upwardLvlOrClass > 0) {
  116. upwardSpaceHeight += calcSubtractSpace(parent, 'bottom');
  117. upward(parent, --upwardLvlOrClass);
  118. }
  119. }
  120. }
  121. }
  122. }
  123. if (isRef(upwardSpace)) {
  124. upward(anchorEl, unref(upwardSpace));
  125. } else {
  126. upward(anchorEl, upwardSpace);
  127. }
  128. let height =
  129. bottomIncludeBody - unref(layoutFooterHeightRef) - unref(offsetHeightRef) - substractHeight - substractSpaceHeight - upwardSpaceHeight;
  130. // compensation height
  131. const calcCompensationHeight = () => {
  132. compensationHeight.elements?.forEach((item) => {
  133. height += getEl(unref(item))?.offsetHeight ?? 0;
  134. });
  135. };
  136. if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) {
  137. calcCompensationHeight();
  138. } else {
  139. calcCompensationHeight();
  140. }
  141. contentHeight.value = height;
  142. }
  143. onMountedOrActivated(() => {
  144. nextTick(() => {
  145. calcContentHeight();
  146. });
  147. });
  148. useWindowSizeFn(
  149. () => {
  150. calcContentHeight();
  151. },
  152. 50,
  153. { immediate: true }
  154. );
  155. watch(
  156. () => [layoutFooterHeightRef.value],
  157. () => {
  158. calcContentHeight();
  159. },
  160. {
  161. flush: 'post',
  162. immediate: true,
  163. }
  164. );
  165. return { redoHeight, setCompensation, contentHeight };
  166. }