teleport 是 Vue 3 新增功能。teleport 就像是多啦A夢的「隨意門」一樣,只要設定了 teleport,就能夠把 DOM 內容隨意塞去畫面上任個地方。例如當使用遮蓋整個網頁畫面的 lightbox 或 modal 元件時,把它們塞到 body 讓渲染就非常方便。
今天來點輕鬆的學習。teleport 的使用很簡單,只需要把要移動的元素包起來即可:
<teleport to="body">
<p> 移動我到 body </p>
</teleport>
Teleport 有兩個屬性:
to
:移動的目的地。disabled
:
true
(預設):元素不會被移動。false
:元素會被移動到 to
指定的位置。使用 to
屬性來指定目的地:
<teleport to="body"></teleport>
<teleport to=".box"></teleport>
<teleport to="#box"></teleport>
簡單示範:
程式碼:
<div class="box"></div>
<button @click="isDisabled = !isDisabled">移動</button>
<teleport to=".box" :disabled="isDisabled">
<p>移動我到 box</p>
</teleport>
data() {
return {
isDisabled: true,
};
}
.box {
background: pink;
height: 300px;
}
本身 p 是在 box 以外:
移動後:
disabled
,保留資料狀態當動態切換 disabled
時,該元素的資料狀態會得以保留,例如我在旁邊加上點擊次數,每次點擊時都會累加 1:
最後,我們可以在多個元素上使用 teleport,並移動到同一目的地:
<div class="box"></div>
<teleport to=".box">
<p>文字 1</p>
</teleport>
<teleport to=".box">
<p>文字 2</p>
</teleport>
結果就會在 .box
裏,依序出現「文字1」、「文字2」
網絡上有很多例子都是示範 fullscreen modal。當我們要設定 fullscreen modal 時,通常會用到 position: fixed
來設置 modal。舉例說:
<div class="box">
<Modal />
</div>
.box
是父層,如果 .box
設定了 transition
、perspective
或 filter
屬性時,<Modal />
就無法以 viewport 作定位,只會以 .box
作定位。以致整個 modal 會跑版。
postion:fixed 的 MDN 解釋:
以打開縮圖瀏覽圖片為例,每個Image
元件裏都有一個 Lightbox
元件,用作放大顯示這個圖片。
App.vue
<template>
<div class="img-area">
<Image v-for="url in urls" :key="url" :url="url" />
</div>
</template>
data() {
return {
urls: [
"https://images.unsplash.com/photo-1632774240308-f54b5e4145ef?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1588&q=80",
"https://images.unsplash.com/photo-1632714657775-232ce19778a7?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1635&q=80",
"https://images.unsplash.com/photo-1632753043704-280dd515e81a?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1587&q=80",
"https://images.unsplash.com/photo-1627322307804-c87605cf3a89?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1665&q=80",
],
};
},
.img-area {
/* 父層加了 filter,以致 lightbox 跑版 */
filter: drop-shadow(16px 16px 20px grey);
}
Image.vue
<template>
<a href="#" @click="isOpen = true">
<img :src="url" alt="" />
</a>
<Lightbox :url="url" v-if="isOpen" @click="closeLightBox" />
</template>
Lightbox.vue
<template>
<div class="lightbox">
<img :src="url" alt="">
</div>
</template>
<style scoped>
.lightbox {
background: rgba(0, 0, 0, 0.3);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
}
img {
width: 500px;
height: 500px;
object-fit: cover;
}
</style>
因為 App.vue .img-area
加了 filter 屬性,因此,Lightbox 不再以 viewport 為錨點,變為以 .img-area
為錨點,以致跑版:
為了解決以上情況,可以使用 teleport 把 Lightbox 元件移動到 body 渲染:
Image.vue
<teleport to="body">
<Lightbox :url="url" v-if="isOpen" @click="closeLightBox" />
</teleport>
結果:
https://codesandbox.io/s/teleport-image-modal-wyrst?file=/src/components/Lightbox.vue:176-442
藉這些機會再練習一下 Vue,改良目前這個範例:
程式碼的改動:
transform
屬性作測試。https://codesandbox.io/s/teleport-image-gallery-k13u5
disabled
屬性,決定是否要移動到 to
屬性所指定的位置。並且會保留資料狀態。Vue 3 - Teleport
Vue3 Composition API - Teleport 瞬移!
重新認識 Vue.js - 編譯作用域與 Slot 插槽