[vue] transition 介紹與巢狀用法


Posted by vii120 on 2022-06-10

前言

transition 是 Vue 提供的一個 built-in component,可以很快速且便利的為動態的元件加上轉場效果,本篇主要會分享 transition 的基本用法和巢狀用法

*註:以下會用 Vue3 的寫法,但大致上與 Vue2 相同,不同之處會附上 Vue2 的用法

transition usage

transition 的使用需要搭配 v-ifv-show 或動態 <component>,其實就是在元件消失或出現時,Vue 會做一些處理,加上特定的 class 來實現轉場動畫。使用上也很簡單,只要在需要動畫的元素外層加上 <Transition>,再寫轉場的 css 即可

<button @click="show = !show">toggle</button>

<Transition>
  <div v-if="show" class="box">something here</div>
</Transition>
.v-enter-active, .v-leave-active {
  transition: opacity 0.5s ease;
}
.v-enter-from, .v-leave-to {
  opacity: 0;
}

class 的用法可以參考官網圖,寫得很清楚,大部分時候我們只需要特別定義最初 .v-enter-from 跟最後 .v-leave-to 這兩個狀態的樣式就好

v-enter-from 在 Vue2 的 class name 是 v-enter

當專案內有多個不同效果的 transition 時,可以用 name 定義,class 原本的 v- 前綴也會變成對應的名稱

<Transition name="fade">
  <div v-if="show" class="box">something here</div>
</Transition>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

如果希望在 DOM 初始化的時候也可以先跑一次動畫,只要加上參數 appear 就可以了

<Transition name="fade" appear>
  <div v-if="show" class="box">something here</div>
</Transition>

transition mode

有時我們的元件使用了 v-if + v-else,加上 transition 就會出現這個突兀的轉場

理想的狀況應該是第一個動畫結束後後,下一個動畫再跑,這時候就可以使用 mode="out-in" 來達成。另外也有 in-out 是下一個動畫先跑,第一個動畫再結束

<Transition name="fade" mode="out-in">
  <div v-if="show" class="box">something here</div>
  <div v-else class="box box2">something there</div>
</Transition>

transition-group

如果 transition 裡要顯示超過一個 DOM,就要改用 transition-group,裡面的每個元素都要加上 key 且不可重複,如果希望這些元素有個外層 DOM 的話,可以加上 tag

<TransitionGroup name="list" tag="section">
  <div v-for="id in idList" :key="id">
    {{ id }}
  </div>
</TransitionGroup>

Vue2 有預設的 wrapper tag <span>,但一樣可以設定其他 tag

transition hooks

transition 本身也提供多個事件,可以在不同時間點執行特定的函式,enterleave 都各自有四種狀態,這邊以 enter 的來說明觸發時機:

  • before-enter:動畫開始前,使用 v-if 的話 dom 尚未載入
  • enter:動畫執行中,使用 v-if 的話 dom 已經載入
  • after-enter:動畫結束後
  • enter-cancelled:動畫取消後。例如:點了顯示按鈕後,區塊還沒完整顯示(還沒觸發 after-enter),就又按了關閉按鈕,這個狀況就會觸發 enter-cancelled 然後接著跑 before-leave

較特別的地方是 leave-cancelled 只有在搭配 v-show 時才可以使用,enter-cancelled 則沒有這個限制

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>

nested transition

有時我們希望區塊出現時,父層跟子層分別有各自的動畫,這種狀況就會寫兩個 transition,舉例來說:我想做出區塊先淡入,文字再由上而下出現。於是我們很直覺的寫了兩層的 transition 並分別寫了不同的 name 和 css,但寫完之後會發現:咦,為何子層的動畫都沒跑到?看起來跟只寫一層 fade transition 的效果一樣阿

<button @click="show = !show">toggle</button>

<Transition name="fade">
  <div v-if="show" class="box">
    <Transition name="fade-down">
      <div v-if="show">something here</div>
    </Transition>
  </div>
</Transition>
/* fade */
.fade-enter-active, .fade-leave-active {
  transition: all 0.5s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

/* fade down */
.fade-down-enter-active, .fade-down-leave-active {
  transition: all 0.5s;
}
.fade-down-enter-from, .fade-down-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}

仔細想想其實也滿合理的,因為兩個 transision 裡都是用同個參數控制顯示 v-if="show",所以兩個動畫會同時跑,看起來就像沒有跑到,這時候就可以用到剛剛介紹的 transition hooks 了。首先,我們需要多設定一個參數來控制裡面的文字顯示,讓動畫可以分開跑,為了統一命名格式,我們把原本的 show 命名改成 initBox(外層),新的參數叫 initText(內層文字),顯示的時候,順序是先跑外層動畫,再跑內層動畫,所以可以在外層的 @enter 事件上將 initText 改為 true,讓外層動畫進行時再觸發內層文字的顯示:

<button @click="initBox = !initBox">toggle</button>

<Transition name="fade" @enter="initText = true">
  <div v-if="initBox" class="box">
    <Transition name="fade-down">
      <div v-if="initText">something here</div>
    </Transition>
  </div>
</Transition>

效果會像這樣:

接下來處理消失的效果,因為內外層消失的動畫順序跟剛剛是相反的,所以 toggle 按鈕的 click 事件也需要改一下,點擊 toggle 時要先將 initText 改為 false,在子層 transition 的 @leave 事件上再把 initBox 改為 false,讓內層動畫進行時再更新外層動畫:

<button @click="handleToggle">toggle</button>

<Transition name="fade" @enter="initText = true">
  <div v-if="initBox" class="box">
    <Transition name="fade-down" @leave="initBox = false">
      <div v-if="initText">something here</div>
    </Transition>
  </div>
</Transition>
const { ref } = Vue;
const App = {
  setup() {
    const initBox = ref(false);
    const initText = ref(false);

    const handleToggle = () => {
      if (!initBox.value) initBox.value = true; // 顯示時先更新 initBox
      else initText.value = false; // 消失時先更新 initText
    }

    return { initBox, initText, handleToggle };
  }
}

效果如下,如果希望消失時兩個動畫再多一點時間差,內層可以改用 @after-leave

覺得意猶未盡的話可以玩玩看這個彈窗效果:因為彈窗可能會疊加多個,為了讓彈窗關閉的那個瞬間,背景遮罩可以先保留,彈窗完全關閉後再顯示下一層彈窗,所以用了巢狀的 transition,控制顯示的部分有結合彈窗陣列做一些處理,也歡迎討論或分享其他做法~

reference


#Vue #transition #vue3 #nested-transition







Related Posts

API串接練習

API串接練習

Deepest Leaves Sum

Deepest Leaves Sum

改善肩背疼痛的一些動作

改善肩背疼痛的一些動作


Comments