现有一个模态框,目前一切正常,Modal可以在视口正确位置展示
<template>
<div class="father">
<h3>模态框的父组件</h3>
<button @click="showModal = !showModal">显示/隐藏modal</button>
<div class="modal" v-if="showModal">
<h3>模态框</h3>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
const showModal = ref(false)
</script>
<style scoped>
.father {
width: 500px;
height: 500px;
background-color: #fdf;
}
.modal {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 100px;
background-color: #dfb;
text-align: center;
}
</style>
但如果将Modal的父元素样式中添加 transform: scale(1) 后,Modal的fixed出错,根据父组件定位。
position: fixed 能够相对于浏览器窗口定位有一个条件,那就是不能有任何祖先元素设置了 transform、perspective 或者 filter 样式属性。
此时需要使用Vue3的内置组件Teleport,将Modal放入body中即可。
<Teleport> 接收一个 to 属性来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue “把以下模板片段传送到 body 标签下”。
<Teleport to="body">
<div class="modal" v-if="showModal">
<h3>模态框</h3>
</div>
</Teleport>
注意
:<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,需要确保在挂载 <Teleport> 之前先挂载该元素。
至此,效果正常。
禁用Teleport
在某些场景下可能需要视情况禁用 <Teleport>。举例来说,在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。可以通过对 <Teleport> 动态地传入一个 disabled 属性来处理这两种不同情况。
<Teleport :disabled="isMobile">
...
</Teleport>
多个Teleport共享目标
一个可重用的模态框组件可能同时存在多个实例。对于此类场景,多个 <Teleport> 组件可以将其内容挂载在同一个目标元素上,而顺序就是简单的顺次追加,后挂载的将排在目标元素下更后面的位置上。
比如下面这样的用例:
<Teleport to="#modals">
<div>A</div>
</Teleport>
<Teleport to="#modals">
<div>B</div>
</Teleport>
渲染的结果为:
<div id="modals">
<div>A</div>
<div>B</div>
</div>