123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- <template>
- <teleport :to="mount">
- <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
- <slot />
- </div>
- </teleport>
- </template>
- <script setup lang="ts">
- // onUpdated
- import { computed, defineExpose, defineProps, onActivated, onUnmounted, reactive, watch } from 'vue'
- import { getPostionByTarget, getScrollParents } from '@kankan-components/utils'
- import { useZIndex } from '@kankan-components/hooks'
- const { currentZIndex } = useZIndex()
- defineOptions({
- name: 'UIFloating',
- })
- const Horizontal = {
- center: 'center',
- right: 'right',
- left: 'left',
- }
- const Vertical = {
- center: 'center',
- top: 'top',
- bottom: 'bottom',
- }
- const Divide = '-'
- const props = defineProps({
- mount: {
- require: true,
- default: document.body,
- },
- class: { type: String },
- refer: { type: Object },
- dire: { type: String },
- width: { type: [Number, String] },
- height: { type: [Number, String] },
- })
- const emit = defineEmits(['leave', 'enter'])
- // 确定方向
- const dires = computed(() => {
- const dire = props.dire || `${Vertical.bottom}${Divide}${Horizontal.left}`
- const isPreset = (preset, val) => Object.keys(preset).some(key => preset[key] === val)
- let [horizontal, vertical] = dire.split(Divide)
- if (!horizontal || !isPreset(Horizontal, horizontal)) {
- horizontal = Horizontal.left
- }
- if (!vertical || !isPreset(Vertical, vertical)) {
- vertical = Vertical.bottom
- }
- return [horizontal, vertical]
- })
- const normalizeUnit = (unit: number | string, total: number): number => {
- if (unit === 0) {
- return 0
- } else if (typeof unit === 'number') {
- return unit ? (unit <= 1 && unit >= 0 ? total * unit : unit) : 0
- } else if (unit.includes('px')) {
- return normalizeUnit(Number.parseFloat(unit), total)
- } else if (unit.includes('%')) {
- return normalizeUnit(Number.parseFloat(unit) / 100, total)
- }
- return 0
- }
- const width = computed(() => props.refer && normalizeUnit(props.width, props.refer.offsetWidth))
- const height = computed(() => props.refer && normalizeUnit(props.height, props.refer.offsetHeight))
- const location = reactive({ x: 0, y: 0 })
- const scrollParents = computed(() => (props.refer ? getScrollParents(props.refer, props.mount) : []))
- watch(
- [scrollParents, props],
- ([newParents], [oldParents]) => {
- oldParents && oldParents.forEach(dom => dom.removeEventListener('scroll', updateLocation))
- newParents.forEach(dom => dom.addEventListener('scroll', updateLocation))
- if (props.refer) {
- setTimeout(() => updateLocation())
- }
- },
- { immediate: true }
- )
- // const zIndex = currentZIndex
- const style = computed(() => {
- const style = {
- width: width.value && `${width.value}px`,
- height: height.value && `${height.value}px`,
- left: `${location.x}px`,
- top: `${location.y}px`,
- zIndex: currentZIndex,
- }
- if (location.x > 0 && location.y > 0) {
- return style
- }
- return {}
- })
- const updateLocation = () => {
- const pos = getPostionByTarget(props.refer, props.mount)
- const screenInfo = scrollParents.value.reduce(
- (t, c) => {
- t.y += c.scrollTop
- t.x += c.scrollLeft
- return t
- },
- { x: 0, y: 0 }
- )
- const [horizontal, vertical] = dires.value
- const start = {
- x: pos.x - screenInfo.x,
- y: pos.y - screenInfo.y,
- }
- switch (horizontal) {
- case Horizontal.left:
- location.x = start.x
- break
- case Horizontal.right:
- location.x = start.x + pos.width
- break
- case Horizontal.center:
- location.x = start.x + pos.width / 2
- break
- }
- switch (vertical) {
- case Vertical.top:
- location.y = start.y
- break
- case Vertical.bottom:
- location.y = start.y + pos.height
- break
- case Vertical.center:
- location.y = start.y + pos.height / 2
- break
- }
- }
- window.addEventListener('resize', updateLocation)
- onUnmounted(() => {
- scrollParents.value.forEach(dom => dom.removeEventListener('scroll', updateLocation))
- window.removeEventListener('resize', updateLocation)
- })
- onActivated(() => {
- if (props.refer) {
- updateLocation()
- }
- })
- defineExpose({
- updateLocation,
- })
- </script>
|