123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- <template>
- <teleport :to="mount">
- <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
- <slot></slot>
- </div>
- </teleport>
- </template>
- <script setup>
- // onUpdated
- import { defineProps, defineExpose, onUnmounted, reactive, watch, computed, onActivated } from 'vue'
- import { getPostionByTarget, getScrollParents, getZIndex } from '../../utils'
- 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, total) => {
- if (unit === void 0) {
- return void 0
- } else if (typeof unit === 'number') {
- return unit ? ((unit <= 1) & (unit >= 0) ? total * unit : unit) : void 0
- } else if (unit.includes('px')) {
- return normalizeUnit(parseFloat(unit), total)
- } else if (unit.includes('%')) {
- return normalizeUnit(parseFloat(unit) / 100, total)
- }
- }
- 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 = getZIndex()
- const style = computed(() => {
- let style = {
- width: width.value && width.value + 'px',
- height: height.value && height.value + 'px',
- left: location.x + 'px',
- top: location.y + 'px',
- zIndex: zIndex,
- }
- 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>
- <script>
- export default { name: 'UiFloating' }
- </script>
|