CollapseBox.vue 2.2 KB

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