こんにちは、猫チーズです。
Vue公式ドキュメント「Enter/Leave とトランジション一覧 - Vue.js」の「トランジションの再利用」を参考にしながら、transition-groupを再利用するコンポーネントを作っていたら、リスト移動トランジションが適用されなくてハマりました。
海外系の質問サイトを漁ってもそもそも同様の問題にぶつかってる人が見当たらなかったので、transition-groupの再利用とscoped CSSを組み合わせること自体レアケースなんでしょうか。
何時間か試行錯誤した結果、タイトルの通り原因が分かったのでここに記しておきます。
動作環境:Vue 2.6.12
問題のコード
問題の部分を簡潔に切り出すと以下のような単一ファイルコンポーネントです。
<template>
<transition-group>
<slot></slot>
</transition-group>
</template>
<script>
// 略
</script>
<style lang='scss' scoped>
.v-move {
transition: transform 400ms;
}
</style>
slotの部分には、呼び出し側でv-forを使って要素たちを並べます。
その要素たちの順番が入れ替わった時には、入れ替わりのトランジションが自動で付く想定です。
ところがこのコードだと、どうしてもトランジションされませんでした。
解決法を探る
❶ slotの代わりに適当な要素を配置して、動的に順番入れ替えしてみる。
→ トランジションされた。単一ファイルコンポーネントだからうまくいかない、という訳ではない。原因はslot周りにある。
❷ 一つのslotに複数要素を読み込ませるのではなくて、要素数分だけslotを動的に用意して、それらをtransition-groupで囲ってみる。
→ トランジションされない。templateの段階でtransition-groupの子要素が複数あればいい、という訳ではない。
❸ templateの代わりにrenderオプションを使い、slotの中身を要素ごとにバラし、それぞれの要素を新しいdivで囲ってtransition-groupの子要素として配置する。
→ トランジションされた。このコンポーネント内で生成された要素がtransition-groupの子要素なら、トランジションされる?
❹ ふと、CSSをカプセル化するためのカスタム属性(data-v-abcd1234みたいなもの)によってtransition-groupの子要素を選別してしまってるのでは?と思い、cssからscopedを除いてみる。
→ トランジションされた。これが原因ぽい。
❺ このコンポーネントに割り当てられたカスタム属性を手動でコピーして、呼び出し元のslotへ読み込む要素たちに手動でカスタム属性を貼り付けてみる。
→ トランジションされた。やっぱりScoped CSS用のカスタム属性がtransition-groupの子要素たちにもつけられていないと、トランジションが適用されない。
❻ scopedを取り除く代わりに、トランジション指定用のCSSのセレクタ .v-move
に/deep/を付ける。
→ トランジションされた。カスタム属性がマッチしてようがマッチしてなかろうが、トランジション中にはtransition-groupの子要素に .v-move
クラスが付けられるらしい。トランジションが適用されてなかったのは、scopedしてるが故にCSSのセレクタ側で暗黙的にカスタム属性 .v-move[data-v-abcd1234]
によって選別してしまっていたからだった。
ということで解決法
コンポーネントのCSSをscopedで安全にカプセル化しつつ、問題の箇所だけ限定的にカプセル化を解くと、下のようなコードになった。
<template>
<transition-group class='items'>
<slot></slot>
</transition-group>
</template>
<script>
// 略
</script>
<style lang='scss' scoped>
.items > /deep/ .v-move {
transition: transform 400ms;
}
</style>
変更したのは2箇所。
1. transition-groupにクラス名を付ける。
2. .v-move
に/deep/を付けてカプセル化を解きつつ、完全に解いてしまうと影響範囲が広いので、 .items >
によってtransition-groupの子要素のみに縛っている。
私が実際に作っているコンポーネントはもう少し複雑なので、影響範囲が最小限となるようにこのようにdeepや子要素縛りなどをしているけど、もっと緩めの環境の人は単にscopedを取るだけでもいいかも。