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 插槽