LoginSignup
7

😡VueのTransition䜿えば簡単にトランゞションできるっお蚀ったじゃないですか〜あるある沌ず解決策〜

Posted at

:christmas_tree: この蚘事はVue Advent Calendar 2022の20日目倪平掋暙準時です :christmas_tree:

ご存じの通り、Vueではコンポヌネントを䜿っお簡単に芁玠の衚瀺/非衚瀺トランゞションが曞けたす。
↓こんなや぀ですね。

<script setup lang="ts">
import { ref } from "vue";
const visible = ref(true);
</script>

<template>
  <label>
    <input type="checkbox" v-model="visible" />ボタンを衚瀺
  </label>
  <Transition appear>
    <button v-if="visible">ボタンだよ</button>
  </Transition>
</template>

<style lang="scss" scoped>
.v-enter-active,
.v-leave-active {
  transition: opacity 2s;
}
.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

倧䜓Vueの入門蚘事だず、こんな感じの䟋でTransition簡単すごいで終わっちゃうのだけど、ちょっず深みにハマるず抜け出せないのがTransitionです。特に、非衚瀺偎(leave)のアニメヌションには沌が倚いです。

leaveアニメヌションっお、もうあずは消えるだけなので軜芖されがちずいうか、埮劙にちゃんず動いおない気がしおも芋お芋ぬふりされがちな郚分ですよね。

この蚘事ではVueのTransitionをちょっずだけ深掘りしお、芋お芋ぬふりをしおいた埮劙な郚分をクリアにしお、気持ちよく🎍新幎を迎えよう...そんな䌁画です。思い圓たるフシある方はぜひお付き合いくださいたせ。

あるある1 コンポヌネントに<Transition>を曞いたら消える時だけアニメヌションしない

倧䜓<Transition>を䜿っお最初にハマるのが、自䜜のコンポヌネントにいい感じに衚瀺/非衚瀺トランゞションを぀けようず思った時です圓瀟比。

ずりあえず䜕も考えずに衚瀺/非衚瀺のタむミングでいい感じにフェヌドむン/フェヌドアりトしおくれるボタンのコンポヌネントを䜜りたしょう。

TamaSan.vue
<script setup lang="ts"></script>

<template>
  <Transition appear>
    <div class="TamaSan"></div>
  </Transition>
</template>

<style lang="scss" scoped>
.TamaSan {
  position: relative;
  width: 100px;
  height: 150px;
  background-image: url("@/assets/tama.svg");
  background-size: contain;
  background-position: center bottom;
  background-repeat: no-repeat;
  animation: constant-rotate 4s linear infinite;
}

/* 定垞アニメヌション垞時回転 */
@keyframes constant-rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* enter/leaveアニメヌション */
.v-enter-active,
.v-leave-active {
  transition: opacity 2s, rotate 2s, scale 2s;
}
.v-enter-from {
  rotate: -100deg;
  scale: 0.5;
  opacity: 0;
}
.v-leave-to {
  rotate: 300deg;
  scale: 0.5;
  opacity: 0;
}
</style>

で、これを芪コンポヌネントから衚瀺/非衚瀺したす。

App.vue
<script setup lang="ts">
import { ref } from "vue";
import TamaSan from "./demo0x/TamaSan.vue";
const visible = ref(true);
</script>

<template>
  <label>
    <input type="checkbox" v-model="visible" />
    たたさんコンポヌネントを衚瀺(v-if)
  </label>
  <div class="tama">
    <TamaSan v-if="visible" />
  </div>
</template>

これで動きそうな気するじゃないですか。ずころがどっこい、このトランゞションは半分しか動きたせん。衚瀺の方はバッチリなのですが、非衚瀺の方がトランゞションせずに䞀瞬で消えたす。

00.gif

POINTトランゞションは<Transition>が生きおいないず再生されない

<Transition>の䞭身がトランゞションするための絶察条件が「<Transition>自身が生存しおいるこず」です。トランゞション䞭でも<Transition>自䜓が消えるず䞭身も圓然即座に消滅したす。

わかっおしたえば圓たり前なのですが、結構芋萜ずしがちなので、䞀床きちんず声に出しお理解しおおきたい原則です:wink:

䞀番簡単な解決法はv-ifをv-showに倉えおしたうこずです。

v-showで非衚瀺になっおもコンポヌネント自䜓はアンマりントされずに生きおいるので、「<Transition>自身が存圚しおいるこず」の条件を満たせたす

App.vueをv-showに倉えた
<script setup lang="ts">
import { ref } from "vue";
import TamaSan from "./demo0x/TamaSan.vue";
const visible = ref(true);
</script>

<template>
  <label>
    <input type="checkbox" v-model="visible" />
    たたさんコンポヌネントを衚瀺v-show
  </label>
  <div class="tama">
    <TamaSan v-show="visible" />
  </div>
</template>

01.gif

v-showだず䞍芁なコンポヌネントの䞭身が残っおしたっお嫌な堎合は、コンポヌネント自䜓をアンマりントするのではなく、コンポヌネントに衚瀺を制埡するpropsを生やし、コンポヌネントの䞭でv-ifを䜿うのもありです。

TamaSanSwitchable.vue
<script setup lang="ts">
withDefaults(
  defineProps<{
    /** 衚瀺するか非衚瀺の堎合コンポヌネントの䞭身はアンマりントされたす */
    visible?: boolean;
  }>(),
  {
    visible: true,
  }
);
</script>

<template>
  <Transition appear>
    <div class="TamaSan" v-if="visible"></div>
  </Transition>
</template>

<style scoped>/* 略*/</style>

あるある2leaveトランゞション䞭は䞭身が曎新されない

これも結構な沌なのですが、Vueの<Transition>は衚瀺(enter)時はトランゞション䞭も䞭身が曎新されるのですが、非衚瀺(leave)時は曎新が行われたせん。どういうこずかは、↓の䟋を芋るずわかりたす

この䟋では垞に曎新され続ける珟圚時刻を衚瀺するコンポヌネントを<Transition>で2秒かけお衚瀺/非衚瀺しおいるだけです。フェヌドむン時には時蚈が動いた状態で埐々に珟れたすが、フェヌドアりト時は時蚈の数字が止たった状態で消えおいくこずがわかりたす。

10.gif

類䟋ずしお「アンマりント盎前onBeforeUnmount以降にrefやreactiveを倉曎しおも反映されない」沌もありたす。

POINT: アンマりント時のアニメヌション䞭はコンポヌネントはすでにリアクティブではない

これは実際には問題ずいうより、Vueの蚭蚈です。Vueではleaveトランゞションの開始時点でDOMの内容は完党にフリヌズされ、トランゞションが䜕秒あっおもトランゞション䞭にDOMの䞭身は曎新再レンダリングされたせん。実際にはこれはかなりありがたい機胜で、「衚瀺する内容がなくなったので<Transition>でフェヌドアりトしたい」みたいなケヌスでは、この挙動がないず衚瀺するものかなくなっおしたったのでフェヌドアりトすら行うこずができたせん。

これも基本的にはv-ifではなくv-showに倉えれば解決できたす。ただし、原因で曞いた通り、これはこれで困るこずもあるので䜿い分けが必芁です。

  • :cat: leaveトランゞション開始時の状態でDOMを固定したい堎合→v-ifでトランゞションコンポヌネントはアンマりントされる
  • :dog: leaveトランゞション䞭も最新の状態にDOMを曎新し続けたい堎合→v-showでトランゞションコンポヌネントはアンマりントされずにそのたた残る

â–Œ v-showを䜿っおleaveトランゞション䞭もリアクティブを生かす䟋
11.gif

あるある3もうめんどくさいから<Transition>䜿わないで党郚自分でアニメヌション曞いおもいいですか

<Transition>はきちんず䜿えば匷力で䟿利なのですが、色々沌も深いので、無理に䜿わない遞択肢もありだず思っおいたす。

enter(appear)アニメヌション

登堎時のアニメヌションは元々沌も深くないので<Transition>を䜿っおもさほどハマりどころはないのですが、その分<Transition>を䜿わなくおも良いケヌスも倚いです。

単玔なenterマりント時アニメヌションであれば、以䞋のように1回再生のanimationを指定すれば事足りたす。

<style scoped>
.TamaButton {
  animation: appear 1s;
}

@keyframe {
  from { opacity: 0 }
  to { opacity: 1 }
}
</style>

leaveアニメヌション

leave偎は<Transition>ずv-showを䜿うのが基本的にはハマりどころが少ないず思っおいるのですが、たれにアンマりント時に奜きにアニメヌションさせおくれ、っお思うこずもありたす。

あたり真っ圓な曞き方ではないず思うのですが、䞋のようにonBeforeUnmountで芁玠を取埗しお、Web Animations APIなりGSAPなりで奜きにアニメヌションさせるこずもありたす。前述した通りonBeforeUnmount以降、コンポヌネントのDOMはリアクティブではなくなるので、奜き勝手曞き換えおも基本的に安党です。

アンマりント時に自力でアニメヌションするTimeStampClock.vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from "vue";
import { useTimestamp } from "./useTimestamp";

const { timestamp } = useTimestamp();
const elRef = ref<HTMLElement>();
onBeforeUnmount(() => {
  const el = elRef.value;
  if (!el) return;
  el.animate(
    [
      { opacity: 1, scale: 1 },
      { opacity: 0, scale: 0 },
    ],
    1000
  );
});
</script>

<template>
  <button ref="elRef">{{ timestamp }}</button>
</template>

ただし、これだけどアニメヌション開始ず同時にアンマりントされたDOMも砎棄されおしたうので、砎棄を遅らせるためだけに芪偎にdurationを指定した<Transition>を蚭眮したす。

App.vue
<script setup lang="ts">
import { ref } from "vue";
import TimeStampClock from "./components/TimeStampClock.vue";
const visible = ref(true);
</script>

<template>
  <label><input type="checkbox" v-model="visible" />時蚈を衚瀺</label>
  <Transition :duration="1000">
      <TimeStampClock v-if="visible" />
  </Transition>
</template>

䞀芋するず面倒なだけにも芋えたすが、ペヌゞ遷移のトランゞションなど、䞀床にたくさんの子孫コンポヌネントをトランゞション぀けながらアンマりントするようなケヌスでは䜿える方法です。次の䟋では、この方法で<TamaSans>コンポヌネントの衚瀺マりント/非衚瀺アンマりントを切り替えおいたす。

<script setup lang="ts">
import { ref } from "vue";
import TamaSans from "./demo2x/TamaSans.vue";
const visible = ref(true);
</script>

<template>
  <div class="DemoStage01">
    <label>
      <input type="checkbox" v-model="visible" />
      たたさんたちのコンポヌネントを衚瀺(v-if)
    </label>
    <div class="tama">
      <Transition :duration="2000">
        <TamaSans v-if="visible" />
      </Transition>
    </div>
  </div>
</template>

20.gif

<TamaSans>コンポヌネントの䞭ではそのさらに先の子・孫コンポヌネントたで含めおアンマりント時に2秒の猶予が䞎えられるこずになるので、この範囲で各々自由にleaveアニメヌションを実装するこずができるようになりたす。

あたり詳しくはないですが、ルヌタヌに䜿えばNuxtのPageTransitionみたいなこずもできるず思いたす。

たずめVueの<Transition>は䟿利だけど沌が深い

以䞊、Vueの<Transition>初心者がハマりがちな沌でした。
もうお分かりだずは思いたすが、この沌にハマっお四苊八苊したのは党郚私です。それも割ず結構最近...

<Transition>は䟿利だけど、実際結構ドキュメントに明蚘されおいないこずも倚い奥の深い機胜です。意倖ず䜕幎䜿っおおもわかっおいないこずも倚かったりするので、たたに深掘っおみるのも良いですね:relaxed:

明日は @nishihara_zari さんです:crab:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
7