index.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <template>
  2. <teleport :to="mount">
  3. <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
  4. <slot></slot>
  5. </div>
  6. </teleport>
  7. </template>
  8. <script setup>
  9. // onUpdated
  10. import { defineProps, defineExpose, onUnmounted, reactive, watch, computed, onActivated } from 'vue'
  11. import { getPostionByTarget, getScrollParents, getZIndex } from '../../utils'
  12. const Horizontal = {
  13. center: 'center',
  14. right: 'right',
  15. left: 'left',
  16. }
  17. const Vertical = {
  18. center: 'center',
  19. top: 'top',
  20. bottom: 'bottom',
  21. }
  22. const Divide = '-'
  23. const props = defineProps({
  24. mount: {
  25. require: true,
  26. default: document.body,
  27. },
  28. class: { type: String },
  29. refer: { type: Object },
  30. dire: { type: String },
  31. width: { type: [Number, String] },
  32. height: { type: [Number, String] },
  33. })
  34. const emit = defineEmits(['leave', 'enter'])
  35. // 确定方向
  36. const dires = computed(() => {
  37. const dire = props.dire || `${Vertical.bottom}${Divide}${Horizontal.left}`
  38. const isPreset = (preset, val) => Object.keys(preset).some(key => preset[key] === val)
  39. let [horizontal, vertical] = dire.split(Divide)
  40. if (!horizontal || !isPreset(Horizontal, horizontal)) {
  41. horizontal = Horizontal.left
  42. }
  43. if (!vertical || !isPreset(Vertical, vertical)) {
  44. vertical = Vertical.bottom
  45. }
  46. return [horizontal, vertical]
  47. })
  48. const normalizeUnit = (unit, total) => {
  49. if (unit === void 0) {
  50. return void 0
  51. } else if (typeof unit === 'number') {
  52. return unit ? ((unit <= 1) & (unit >= 0) ? total * unit : unit) : void 0
  53. } else if (unit.includes('px')) {
  54. return normalizeUnit(parseFloat(unit), total)
  55. } else if (unit.includes('%')) {
  56. return normalizeUnit(parseFloat(unit) / 100, total)
  57. }
  58. }
  59. const width = computed(() => props.refer && normalizeUnit(props.width, props.refer.offsetWidth))
  60. const height = computed(() => props.refer && normalizeUnit(props.height, props.refer.offsetHeight))
  61. const location = reactive({ x: 0, y: 0 })
  62. const scrollParents = computed(() => (props.refer ? getScrollParents(props.refer, props.mount) : []))
  63. watch(
  64. [scrollParents, props],
  65. ([newParents], [oldParents]) => {
  66. oldParents && oldParents.forEach(dom => dom.removeEventListener('scroll', updateLocation))
  67. newParents.forEach(dom => dom.addEventListener('scroll', updateLocation))
  68. if (props.refer) {
  69. setTimeout(() => updateLocation())
  70. }
  71. },
  72. { immediate: true }
  73. )
  74. const zIndex = getZIndex()
  75. const style = computed(() => {
  76. let style = {
  77. width: width.value && width.value + 'px',
  78. height: height.value && height.value + 'px',
  79. left: location.x + 'px',
  80. top: location.y + 'px',
  81. zIndex: zIndex,
  82. }
  83. if (location.x > 0 && location.y > 0) {
  84. return style
  85. }
  86. return {}
  87. })
  88. const updateLocation = () => {
  89. const pos = getPostionByTarget(props.refer, props.mount)
  90. const screenInfo = scrollParents.value.reduce(
  91. (t, c) => {
  92. t.y += c.scrollTop
  93. t.x += c.scrollLeft
  94. return t
  95. },
  96. { x: 0, y: 0 }
  97. )
  98. const [horizontal, vertical] = dires.value
  99. const start = {
  100. x: pos.x - screenInfo.x,
  101. y: pos.y - screenInfo.y,
  102. }
  103. switch (horizontal) {
  104. case Horizontal.left:
  105. location.x = start.x
  106. break
  107. case Horizontal.right:
  108. location.x = start.x + pos.width
  109. break
  110. case Horizontal.center:
  111. location.x = start.x + pos.width / 2
  112. break
  113. }
  114. switch (vertical) {
  115. case Vertical.top:
  116. location.y = start.y
  117. break
  118. case Vertical.bottom:
  119. location.y = start.y + pos.height
  120. break
  121. case Vertical.center:
  122. location.y = start.y + pos.height / 2
  123. break
  124. }
  125. }
  126. window.addEventListener('resize', updateLocation)
  127. onUnmounted(() => {
  128. scrollParents.value.forEach(dom => dom.removeEventListener('scroll', updateLocation))
  129. window.removeEventListener('resize', updateLocation)
  130. })
  131. onActivated(() => {
  132. if (props.refer) {
  133. updateLocation()
  134. }
  135. })
  136. defineExpose({
  137. updateLocation,
  138. })
  139. </script>
  140. <script>
  141. export default { name: 'UiFloating' }
  142. </script>