一、分析一波🌝🌝🌝
popup
是独立的组件,放到body下面popup
含两部分内容,一部分为背景蒙版,一部分为内容的包裹容器popup
通过双向绑定的值进行控制展示和隐藏popup
展示时,页面滚动应该被锁定- 内容区域应该接收所有的
attrs
,并且应该通过插槽让调用方指定其内容
二、构建🌪️🌪️🌪️
1. 新建 src/libs/popup/index.vue
文件
小知识点:v-model双向数据绑定,底层机制
defineModel
是一个便利宏。 编译器将其展开为以下内容:
- 一个名为
modelValue
的 prop,本地 ref 的值与其同步; - 一个名为
update:modelValue
的事件,当本地 ref 的值发生变更时触发
xml
<template>
<div>
<!-- teleport -->
<teleport to="body">
<!-- 蒙版 -->
<transition name="fade">
<div
v-if="modelValue"
class="w-screen h-screen bg-zinc-900/80 z-40 fixed top-0 left-0"
@click="emits('update:modelValue', false)"
></div>
</transition>
<!-- 内容 -->
<transition name="popup-down-up">
<div
v-if="modelValue"
v-bind="$attrs"
class="w-screen bg-white z-50 fixed bottom-0"
>
<slot />
</div>
</transition>
</teleport>
</div>
</template>
<script setup>
import { useScrollLock } from '@vueuse/core'
import { watch } from 'vue'
const props = defineProps({
modelValue: {
required: true,
type: Boolean
}
})
const emits = defineEmits(['update:modelValue'])
// ------ 滚动锁定 ------
const isLocked = useScrollLock(document.body)
watch(
() => props.modelValue,
(val) => {
isLocked.value = val
},
{
immediate: true
}
)
</script>
<style lang="scss" scoped>
// fade 展示动画
.fade-enter-active {
transition: all 0.3s;
}
.fade-leave-active {
transition: all 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
// popup-down-up 展示动画
.popup-down-up-enter-active {
transition: all 0.3s;
}
.popup-down-up-leave-active {
transition: all 0.3s;
}
.popup-down-up-enter-from,
.popup-down-up-leave-to {
transform: translateY(100%);
}
</style>
优化一下:
v-model
双向数据绑定,由于不可以在子组件直接修改响应式数据,所以有一些不太方便 ,那应该怎么优化呢?可以使用vueuse
提供的 useVModel,通过 useVModel 获取到响应式数据,当响应式数据的状态变化时,自动触发 update:modelValue
xml
<template>
<!-- 无需主动触发 事件 -->
<div
v-if="isOpen"
class="w-screen h-screen bg-zinc-900/80 z-40 fixed top-0 left-0"
@click="isOpen = false"
></div>
<div
v-if="isOpen"
v-bind="$attrs"
class="w-screen bg-white z-50 fixed bottom-0"
>
<slot />
</div>
...
</template>
<script setup>
import { useVModel } from '@vueuse/core'
...
// 不需要主动触发了
// const emits = defineEmits(['update:modelValue'])
// 通过 useVModel 获取到响应式数据 isOpen
const isOpen = useVModel(props)
...
// 监听 isOpen 的变化即可
watch(
isOpen,
...
)
</script>
2. 小试牛刀 demo.vue
xml
<template>
<m-popup v-model="isOpenPopup">
<div>测试m-popup</div>
</m-popup>
</template>
<script setup>
// 控制popup显示/隐藏
const isOpenPopup = ref(true);
</script>