什么是Popper组件?
即使窗口变化、滚动,也不会让弹出框被遮挡
这是一个非常能让用户体验提升的功能
想实现这个功能其实并不困难
实现Popper组件
首先,给大家推荐一下 popper.js
popper.js 是个轻量级强大定位引擎利器
当然,他同时也被Element UI 作为 dependencies(打包依赖)使用
安装:
npm i @popperjs/core
使用:
import { createPopper } from '@popperjs/core'
createPopper(reference, popper, {
// options
})
reference 是挂载元素DOM
popper 是弹出元素DOM
接下来编写 Popper 组件
<template>
<div
class="reference"
ref="referenceRef"
@click.stop.prevent="togglePopperShow"
>
<slot></slot>
</div>
<div
ref="popperRef"
v-show="visible"
>
<slot name="popper"></slot>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, unref, onMounted, watch, nextTick, onUnmounted } from 'vue'
import { createPopper } from '@popperjs/core'
export default defineComponent({
name: 'Popper',
props: {
visible: {
type: Boolean,
default: false
}
},
emits: ['update:visible', 'hide', 'show'],
setup (props, { emit }) {
const referenceRef = ref<HTMLElement>(null)
const popperRef = ref<HTMLElement>(null)
let popperInstance = null
// 创建 popper 实例
const createPopperInstance = () => {
popperInstance = createPopper(unref(referenceRef), unref(popperRef), {
modifiers: [
{
name: 'offset',
options: {
// 偏移值 左右,上下
offset: [0, 5]
}
}
]
})
nextTick(() => {
// 异步更新
popperInstance.update()
})
}
// 销毁 popper 实例
const destroyPopperInstance = () => {
popperInstance?.destroy?.()
popperInstance = null
}
watch(() => props.visible,(visible) => {
if (visible) {
createPopperInstance()
emit('show')
} else {
emit('hide')
}
})
onMounted(() => {
createPopperInstance()
})
onUnmounted(() => {
destroyPopperInstance()
})
const togglePopperShow = () => {
emit('update:visible', !props.visible)
}
return {
referenceRef,
popperRef,
togglePopperShow
}
}
})
</script>
引用 Popper 组件
<template>
<popper v-model:visible="show">
<div class="box"></div>
<template #popper>
<div class="popper"></div>
</template>
</popper>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Popper from './Popper.vue'
export default defineComponent({
components: {
Popper
},
setup() {
const show = ref(false)
return {
show
}
}
})
</script>
<style scoped lang="less">
.box {
width: 100px;
height: 100px;
background: pink;
}
.popper {
width: 100px;
height: 50px;
background: lightskyblue;
}
</style>
好了,效果就出来了
但是有些僵硬,那就加些动画
<!-- Popper 组件 -->
<!-- 省略部分代码 -->
<transition>
<div
ref="popperRef"
v-show="visible"
>
<slot name="popper"></slot>
</div>
</transition>
<!-- 省略部分代码 -->
<style scoped lang="less">
.reference {
display: inline-block;
}
.v-enter-active,
.v-leave-active {
transition: opacity .3s ease-out, transform .3s ease-out;
opacity:1;
transform: scaleY(1);
transform-origin: center top;
}
[data-popper-placement='top'] {
transform-origin: center bottom;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: scaleY(0);
}
</style>
发现动画并不生效,同时有警告
Popper: Detected CSS transitions on at least one of the following CSS properties: "transform", "top", "right", "bottom", "left".
Disable the "computeStyles" modifier's `adaptive` option to allow for smooth transitions, or remove these properties from the CSS transition declaration on the popper element if only transitioning opacity or background-color for example.
说的是:我们在Popper元素上设置了 transform 样式
那我们就按照他的提示,设置一下Popper参数
// 省略部分代码
// 创建 popper 实例
const createPopperInstance = () => {
popperInstance = createPopper(unref(referenceRef), unref(popperRef), {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 5]
}
},
{
// 禁用计算样式,使用 transition 动画
name: 'computeStyles',
options: {
gpuAcceleration: false,
adaptive: false
}
}
]
})
nextTick(() => {
// 异步更新
popperInstance.update()
})
}
完成,很丝滑~
继续测试一下
好,完美~
完整代码
<template>
<div
class="reference"
ref="referenceRef"
@click.stop.prevent="togglePopperShow"
>
<slot></slot>
</div>
<transition>
<div
ref="popperRef"
v-show="visible"
>
<slot name="popper"></slot>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent, ref, unref, onMounted, watch, nextTick, onUnmounted } from 'vue'
import { createPopper } from '@popperjs/core'
export default defineComponent({
name: 'Popper',
props: {
visible: {
type: Boolean,
default: false
}
},
emits: ['update:visible', 'hide', 'show'],
setup (props, { emit }) {
const referenceRef = ref<HTMLElement>(null)
const popperRef = ref<HTMLElement>(null)
let popperInstance = null
// 创建 popper 实例
const createPopperInstance = () => {
popperInstance = createPopper(unref(referenceRef), unref(popperRef), {
modifiers: [
{
// 偏移值 左右,上下
name: 'offset',
options: {
offset: [0, 5]
}
},
{
// 禁用计算样式,使用 transition 动画
name: 'computeStyles',
options: {
gpuAcceleration: false,
adaptive: false
}
}
]
})
nextTick(() => {
// 异步更新
popperInstance.update()
})
}
// 销毁 popper 实例
const destroyPopperInstance = () => {
popperInstance?.destroy?.()
popperInstance = null
}
// 监听 visible 属性
watch(() => props.visible,(visible) => {
if (visible) {
createPopperInstance()
emit('show')
} else {
emit('hide')
}
})
onMounted(() => {
createPopperInstance()
})
onUnmounted(() => {
destroyPopperInstance()
})
const togglePopperShow = () => {
emit('update:visible', !props.visible)
}
return {
referenceRef,
popperRef,
togglePopperShow
}
}
})
</script>
<style scoped lang="less">
.reference {
display: inline-block;
}
.v-enter-active,
.v-leave-active {
transition: opacity .3s ease-out, transform .3s ease-out;
opacity:1;
transform: scaleY(1);
transform-origin: center top;
}
[data-popper-placement='top'] {
transform-origin: center bottom;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: scaleY(0);
}
</style>
感谢阅读