CollapseBox.vue 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. <template>
  2. <view
  3. :id="id"
  4. class="nana-collapse-box"
  5. :style="{
  6. display: realOpenState ? '' : 'none',
  7. height: animDuration > 0 && targetHeight >= 0 ? `${targetHeight}px` : undefined,
  8. transition: animDuration > 0 ? `height ${animDuration}ms ease-in-out` : undefined,
  9. }"
  10. >
  11. <slot />
  12. </view>
  13. </template>
  14. <script setup lang="ts">
  15. import { RandomUtils } from '@imengyu/imengyu-utils';
  16. import { computed, getCurrentInstance, nextTick, ref, watch } from 'vue';
  17. export interface CollapseBoxProps {
  18. /**
  19. * 开启状态
  20. */
  21. open: boolean;
  22. /**
  23. * 动画时长(ms),为0时禁用动画
  24. * @default 300
  25. */
  26. animDuration?: number;
  27. /**
  28. * 名称,用于唯一标识
  29. */
  30. name?: string;
  31. }
  32. const id = computed(() => `nana-collapse-box-${props.name}-${RandomUtils.genNonDuplicateIDHEX(16)}`);
  33. const props = withDefaults(defineProps<CollapseBoxProps>(), {
  34. animDuration: 300,
  35. });
  36. const realOpenState = ref(false);
  37. const targetHeight = ref(0);
  38. const instance = getCurrentInstance();
  39. let isAnimWorking = false;
  40. watch(() => props.open, (newVal) => {
  41. if (props.animDuration <= 0) {
  42. realOpenState.value = newVal;
  43. return;
  44. }
  45. if (isAnimWorking)
  46. return;
  47. if (newVal) {
  48. realOpenState.value = true;
  49. targetHeight.value = -1;
  50. isAnimWorking = true;
  51. nextTick(() => {
  52. uni.createSelectorQuery()
  53. // #ifdef MP
  54. .in(instance)
  55. // #endif
  56. .select(`#${id.value}`)
  57. .boundingClientRect(rect => {
  58. const ref = rect instanceof Array ? rect[0] : rect;
  59. const height = ref?.height || 500;
  60. targetHeight.value = 0;
  61. setTimeout(() => {
  62. targetHeight.value = height;
  63. setTimeout(() => {
  64. isAnimWorking = false;
  65. }, props.animDuration);
  66. }, 10);
  67. })
  68. .exec()
  69. })
  70. } else {
  71. isAnimWorking = true;
  72. targetHeight.value = 0;
  73. setTimeout(() => {
  74. realOpenState.value = false;
  75. isAnimWorking = false;
  76. }, props.animDuration);
  77. }
  78. });
  79. </script>
  80. <style lang="scss">
  81. .nana-collapse-box {
  82. overflow: hidden;
  83. will-change: height;
  84. }
  85. </style>