useContentHeight.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { ComputedRef, 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. export interface CompensationHeight {
  7. // 使用 layout Footer 高度作为判断补偿高度的条件
  8. useLayoutFooter: boolean;
  9. // refs HTMLElement
  10. elements?: Ref[];
  11. }
  12. /**
  13. * 动态计算内容高度,根据锚点dom最下坐标到屏幕最下坐标,根据传入dom的高度、padding、margin等值进行动态计算
  14. * 最终获取合适的内容高度
  15. *
  16. * @param flag 用于开启计算的响应式标识
  17. * @param anchorRef 锚点组件 Ref<ElRef | ComponentRef>
  18. * @param subtractHeightRefs 待减去高度的组件列表 Ref<ElRef | ComponentRef>
  19. * @param substractSpaceRefs 待减去空闲空间(margins/paddings)的组件列表 Ref<ElRef | ComponentRef>
  20. * @param offsetHeightRef 计算偏移的响应式高度,计算高度时将直接减去此值
  21. * @returns 响应式高度
  22. */
  23. export function useContentHeight(
  24. flag: ComputedRef<Boolean>,
  25. anchorRef: Ref,
  26. subtractHeightRefs: Ref[],
  27. substractSpaceRefs: Ref[],
  28. offsetHeightRef: Ref<number> = ref(0)
  29. ) {
  30. const contentHeight: Ref<Nullable<number>> = ref(null);
  31. const { footerHeightRef: layoutFooterHeightRef } = useLayoutHeight();
  32. let compensationHeight: CompensationHeight = {
  33. useLayoutFooter: true,
  34. };
  35. const setCompensation = (params: CompensationHeight) => {
  36. compensationHeight = params;
  37. };
  38. function redoHeight() {
  39. nextTick(() => {
  40. calcContentHeight();
  41. });
  42. }
  43. function calcSubtractSpace(element: HTMLDivElement | null | undefined): number {
  44. let subtractHeight = 0;
  45. const ZERO_PX = '0px';
  46. let marginBottom = ZERO_PX;
  47. let marginTop = ZERO_PX;
  48. if (element) {
  49. const cssStyle = getComputedStyle(element);
  50. marginBottom = cssStyle?.marginBottom ?? ZERO_PX;
  51. marginTop = cssStyle?.marginTop ?? ZERO_PX;
  52. }
  53. if (marginBottom) {
  54. const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
  55. subtractHeight += contentMarginBottom;
  56. }
  57. if (marginTop) {
  58. const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
  59. subtractHeight += contentMarginTop;
  60. }
  61. return subtractHeight;
  62. }
  63. function getEl(element: any): Nullable<HTMLDivElement> {
  64. if (element == null) {
  65. return null;
  66. }
  67. return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement;
  68. }
  69. async function calcContentHeight() {
  70. if (!flag.value) {
  71. return;
  72. }
  73. // Add a delay to get the correct height
  74. await nextTick();
  75. const wrapperEl = getEl(unref(anchorRef));
  76. if (!wrapperEl) {
  77. return;
  78. }
  79. const { bottomIncludeBody } = getViewportOffset(wrapperEl);
  80. // substract elements height
  81. let substractHeight = 0;
  82. subtractHeightRefs.forEach((item) => {
  83. substractHeight += getEl(unref(item))?.offsetHeight ?? 0;
  84. });
  85. // subtract margins / paddings
  86. let substractSpaceHeight = 0;
  87. substractSpaceRefs.forEach((item) => {
  88. substractSpaceHeight += calcSubtractSpace(getEl(unref(item)));
  89. });
  90. let height =
  91. bottomIncludeBody -
  92. unref(layoutFooterHeightRef) -
  93. unref(offsetHeightRef) -
  94. substractHeight -
  95. substractSpaceHeight;
  96. // compensation height
  97. const calcCompensationHeight = () => {
  98. compensationHeight.elements?.forEach((item) => {
  99. height += getEl(unref(item))?.offsetHeight ?? 0;
  100. });
  101. };
  102. if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) {
  103. calcCompensationHeight();
  104. } else {
  105. calcCompensationHeight();
  106. }
  107. contentHeight.value = height;
  108. }
  109. onMountedOrActivated(() => {
  110. nextTick(() => {
  111. calcContentHeight();
  112. });
  113. });
  114. useWindowSizeFn(
  115. () => {
  116. calcContentHeight();
  117. },
  118. 50,
  119. { immediate: true }
  120. );
  121. watch(
  122. () => [layoutFooterHeightRef.value],
  123. () => {
  124. calcContentHeight();
  125. },
  126. {
  127. flush: 'post',
  128. immediate: true,
  129. }
  130. );
  131. return { redoHeight, setCompensation, contentHeight };
  132. }