组件分析
popover组件有两个部分构成,一个是触发弹出层弹出的显示层。一个是弹出层。 一个插槽对应一个插槽。
弹出层绑定鼠标移入和移出事件
代码
<script>
const PROP_TOP_LEFT = 'top-left'
const PROP_TOP_RIGHT = 'top-right'
const PROP_BOTTOM_LEFT = 'bottom-left'
const PROP_BOTTOM_RIGHT = 'bottom-right'
// 定义指定位置的 Enum
const placementEnum = [
PROP_TOP_LEFT,
PROP_TOP_RIGHT,
PROP_BOTTOM_LEFT,
PROP_BOTTOM_RIGHT
]
</script>
<script setup>
import {
ref,
watch,
nextTick
} from 'vue'
// 控制延迟关闭
let timeout = null
const isVisable = ref(false)
// 鼠标移入移除
const mouseenter = () => {
isVisable.value = true
// 再次触发时,清理延时装置
if (timeout) {
clearTimeout(timeout)
}
}
const mouseleave = () => {
timeout = setTimeout(() => {
isVisable.value = false
timeout = null
}, 300)
}
const props = defineProps({
placement: {
type: String,
default: PROP_TOP_LEFT,
validator(val) {
const result =
placementEnum.includes(val)
if (!result) {
throw new Error(
`placement must be one of ${placementEnum}`
)
}
return result
}
}
})
// 展示气泡位置
const contentStyle = ref({
top: 0,
left: 0
})
const referenceTarget = ref(null)
const contentTarget = ref(null)
const useElementSize = (target) => {
if (!target) return {}
return {
width: target.offsetWidth,
height: target.offsetHeight
}
}
/**
* 监听展示的变化,在展示时计算气泡位置
*/
watch(isVisable, (val) => {
if (!val) {
return
}
// 等待渲染成功之后
nextTick(() => {
switch (props.placement) {
// 左上
case PROP_TOP_LEFT:
contentStyle.value.top = 0
contentStyle.value.left =
-useElementSize(
contentTarget.value
).width + 'px'
break
// 右上
case PROP_TOP_RIGHT:
contentStyle.value.top = 0
contentStyle.value.left =
useElementSize(
referenceTarget.value
).width + 'px'
break
// 左下
case PROP_BOTTOM_LEFT:
contentStyle.value.top =
useElementSize(
referenceTarget.value
).height + 'px'
contentStyle.value.left =
-useElementSize(
contentTarget.value
).width + 'px'
break
// 右下
case PROP_BOTTOM_RIGHT:
contentStyle.value.top =
useElementSize(
referenceTarget.value
).height + 'px'
contentStyle.value.left =
useElementSize(
referenceTarget.value
).width + 'px'
break
}
})
})
</script>
<template>
<div
class="relative"
@mouseenter="mouseenter"
@mouseleave="mouseleave">
<div ref="referenceTarget">
<!-- 显示层 -->
<slot name="reference"></slot>
</div>
<transition name="slide">
<div
ref="contentTarget"
v-show="isVisable"
class="absolute p-1 z-20 bg-white border rounded-md"
:style="contentStyle">
<slot></slot>
</div>
</transition>
</div>
</template>
<style scoped>
.slide-enter-active {
transition: all 0.5s;
}
.slide-leave-active {
transition: all.5s;
}
.slide-enter-from,
.slide-leave-to {
opacity: 0;
transform: translateY(-10px);
}
</style>
后续完善
加入动画、加入控制弹出层显示位置的方法。
备注
鼠标的移入移出需要做延时处理。