wd-resize.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. <template>
  2. <view :class="`wd-resize ${customClass}`" :style="rootStyle">
  3. <!--插槽需要脱离父容器文档流,防止父容器固宽固高,进而导致插槽大小被被父容器限制-->
  4. <view :id="resizeId" :class="`wd-resize__container ${customContainerClass}`">
  5. <!--被监听的插槽-->
  6. <slot />
  7. <!--监听插槽变大-->
  8. <scroll-view
  9. class="wd-resize__wrapper"
  10. :scroll-y="true"
  11. :scroll-top="expandScrollTop"
  12. :scroll-x="true"
  13. :scroll-left="expandScrollLeft"
  14. @scroll="onScrollHandler"
  15. >
  16. <view class="wd-resize__wrapper--placeholder" style="height: 100000px; width: 100000px"></view>
  17. </scroll-view>
  18. <!--监听插槽变小-->
  19. <scroll-view
  20. class="wd-resize__wrapper"
  21. :scroll-y="true"
  22. :scroll-top="shrinkScrollTop"
  23. :scroll-x="true"
  24. :scroll-left="shrinkScrollLeft"
  25. @scroll="onScrollHandler"
  26. >
  27. <view class="wd-resize__wrapper--placeholder" style="height: 250%; width: 250%"></view>
  28. </scroll-view>
  29. </view>
  30. </view>
  31. </template>
  32. <script lang="ts">
  33. export default {
  34. name: 'wd-resize',
  35. options: {
  36. virtualHost: true,
  37. addGlobalClass: true,
  38. styleIsolation: 'shared'
  39. }
  40. }
  41. </script>
  42. <script lang="ts" setup>
  43. import { computed, getCurrentInstance, onMounted, ref } from 'vue'
  44. import { addUnit, objToStyle, uuid } from '../common/util'
  45. import { resizeProps } from './types'
  46. const props = defineProps(resizeProps)
  47. const emit = defineEmits(['resize'])
  48. const expandScrollTop = ref<number>(0)
  49. const shrinkScrollTop = ref<number>(0)
  50. const expandScrollLeft = ref<number>(0)
  51. const shrinkScrollLeft = ref<number>(0)
  52. const height = ref<number>(0)
  53. const width = ref<number>(0)
  54. const scrollEventCount = ref<number>(0)
  55. const rootStyle = computed(() => {
  56. const style: Record<string, string | number> = {
  57. width: addUnit(width.value),
  58. height: addUnit(height.value)
  59. }
  60. return `${objToStyle(style)}${props.customStyle}`
  61. })
  62. let onScrollHandler = () => {}
  63. const { proxy } = getCurrentInstance() as any
  64. const resizeId = ref<string>(`resize${uuid()}`)
  65. onMounted(() => {
  66. // 初始化数据获取
  67. const query = uni.createSelectorQuery().in(proxy).select(`#${resizeId.value}`).boundingClientRect()
  68. query.exec(([res]) => {
  69. // 闭包记录容器高度
  70. let lastHeight = res.height
  71. let lastWidth = res.width
  72. // 立即填充父容器高宽
  73. height.value = lastHeight
  74. width.value = lastWidth
  75. // 监听滚动事件
  76. onScrollHandler = () => {
  77. const query = uni.createSelectorQuery().in(proxy).select(`#${resizeId.value}`).boundingClientRect()
  78. query.exec(([res]) => {
  79. // 前两次滚动事件被触发,说明 created 的修改已渲染,通知用户代码当前容器大小
  80. if (scrollEventCount.value++ === 0) {
  81. const result: Record<string, string | number> = {}
  82. ;['bottom', 'top', 'left', 'right', 'height', 'width'].forEach((propName) => {
  83. result[propName] = res[propName]
  84. })
  85. emit('resize', result)
  86. }
  87. // 滚动条拉到底部会触发两次多余的事件,屏蔽掉。
  88. if (scrollEventCount.value < 3) return
  89. // 手动设置父容器高宽,防止父容器坍塌
  90. // 滚动完,重新获取容器新的高度
  91. const newHeight = res.height
  92. const newWidth = res.width
  93. // 立即填充父容器高宽
  94. height.value = newHeight
  95. width.value = newWidth
  96. // 宽高都改变时,只需要触发一次 size 事件
  97. const emitStack: number[] = []
  98. if (newHeight !== lastHeight) {
  99. lastHeight = newHeight
  100. emitStack.push(1)
  101. }
  102. if (newWidth !== lastWidth) {
  103. lastWidth = newWidth
  104. emitStack.push(1)
  105. }
  106. if (emitStack.length !== 0) {
  107. const result: Record<string, any> = {}
  108. ;['bottom', 'top', 'left', 'right', 'height', 'width'].forEach((propName) => {
  109. result[propName] = res[propName]
  110. })
  111. emit('resize', result)
  112. }
  113. // 滚动条拉到底部(如果使用 nextTick 效果更佳)
  114. scrollToBottom({
  115. lastWidth: lastWidth,
  116. lastHeight: lastHeight
  117. })
  118. })
  119. }
  120. // 滚动条拉到底部(如果使用 nextTick 效果更佳)
  121. scrollToBottom({
  122. lastWidth: lastWidth,
  123. lastHeight: lastHeight
  124. })
  125. })
  126. })
  127. function scrollToBottom({ lastWidth, lastHeight }: { lastWidth: number; lastHeight: number }) {
  128. expandScrollTop.value = 100000 + lastHeight
  129. shrinkScrollTop.value = 3 * height.value + lastHeight
  130. expandScrollLeft.value = 100000 + lastWidth
  131. shrinkScrollLeft.value = 3 * width.value + lastWidth
  132. }
  133. </script>
  134. <style lang="scss" scoped>
  135. @import './index.scss';
  136. </style>