戴兜

戴兜的小屋

Coding the world.
github
bilibili
twitter

!importantは、TransitionGroupの機能を無効にします。

image

もし Vue に触れたことがあるなら、おそらく TransitionGroup コンポーネントについて何かしらの知識を持っているでしょう。この記事では、TransitionGroup 内の「移動アニメーション」の使用方法について説明します。

TransitionGroup の「移動アニメーション」についてまだよく理解していないかもしれませんので、ここで簡単に説明します。通常、.[name]-moveクラスに遷移のスタイルを指定する必要があります。例えば、transition: all 0.5s ease;のように指定します。これにより、TransitionGroup 内の要素の位置が変更された場合、Vue は位置が変更された要素を古い位置から新しい位置にスムーズに移動させようとします。もちろん、Vue は要素の追加や削除にも遷移効果をサポートしています。[name]-enter-from[name]-leave-toクラスにスタイルを指定するだけで、要素の追加や削除の遷移効果を実現できます。ただし、これはこの記事の重点ではないため、詳細な説明は省略します。

私も以前は、スタイルを書いて問題なく終わらせていました。

しかし、グループチャットで「要素に常に!importantを含む優先度の高いtransitionスタイルを追加すると、遷移が機能しなくなる」という情報を聞いたとき、私は固まってしまいました😧。当時の私にとっては理解が難しいことでした。遷移時に Vue が要素に[name]-moveを追加するため、なぜ要素に優先度の高いtransitionスタイルを事前に追加すると、遷移が機能しなくなるのでしょうか?

ソースコードを見てみる#

TransitionGroup.tsには、TransitionGroup に関連するコードが記述されています。

初期化フェーズ#

Vue はレンダリング関数内で、子要素の配列childrensetup関数のスコープ内の変数prevChildrenに代入しています (L113)。同時に、prevChildrenを for ループで走査し、各子要素の位置情報をpositionMapに保存しています(古い位置)(L135)。

この初期化フェーズで私たちが議論している内容に関連するのは、これら 2 つの箇所です。prevChildrenの代入により、Vue は更新フェーズで子要素の参照を取得し、関連する操作を行うことができます。また、positionMapにより、Vue は後続の操作で要素の元の位置を取得することができます。

ここでのpositionMapは WeakMap です。Vue は要素オブジェクトをキーとして使用し、要素が破棄された後にpositionMap内の対応する要素の位置情報が適時に自動的に解放されるようにしています。

更新フェーズ#

TransitionGroup 内の子要素が変更されると、登録されたonUpdatedコールバックが呼び出されます。同時に、Vue は遷移に必要なほとんどの操作を完了します。

まず、Vue は forEach を使用して再度各要素の位置情報を取得し、newPositionMapに保存します (L72)。この時点で、要素の新しい位置と古い位置がnewPositionMappositionMapにそれぞれ保存されます。私たちが行う必要があるのは、要素を古い位置から新しい位置にスムーズに移動させることです。

次に、重要な部分です:更新フェーズである以上、要素は既に新しい位置に存在しているはずです。なぜなら、要素が新しい位置に移動している間に遷移が行われるはずがないからです。したがって、私たちがするべきことは、要素を単に古い位置から新しい位置に移動させるだけではありません。代わりに、既に新しい位置にある要素を再び古い位置に戻し、その後新しい位置にスムーズに戻すことです。では、Vue はどのようにこれを実現しているのでしょうか?

L73のコードは次のようになっています。

const movedChildren = prevChildren.filter(applyTranslation) 

Vue はapplyTranslation関数を使用して移動が必要な子要素の配列movedChildrenをフィルタリングしています。関数と変数の命名を見ると、フィルタリングの際に Vue が要素に対していくつかの操作を行っていることがほぼ確実です。

function applyTranslation(c: VNode): VNode | undefined {
  const oldPos = positionMap.get(c)!
  const newPos = newPositionMap.get(c)!
  const dx = oldPos.left - newPos.left
  const dy = oldPos.top - newPos.top
  if (dx || dy) {
    const s = (c.el as HTMLElement).style
    s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`
    s.transitionDuration = '0s'
    return c
  }
}

移動が必要な要素に対して、Vue は新しい位置と古い位置の差を計算し、Transformプロパティを使用して要素を古い位置に戻します。特に注意すべきは、s.transitionDuration = '0s'です。~ 後で考える必要がある~。

しかし、実際には、この時点で要素はまだ古い位置に戻っていません。ブラウザはスタイルの変更をレンダリングキューに追加し、即座にレンダリングされません。ここでブラウザのリフロー (reflow) に関連する知識が関係してきますので、関連する記事を検索して読むことをお勧めします。

要素を即座に古い位置に配置するためには、L73 でmovedPositionを取得した後、Vue はforceReflow関数を実行してリフローを強制的にトリガーします (L76)。forceReflow関数の内容も非常にシンプルです (Transition.ts#L461)。

function forceReflow() {
  return document.body.offsetHeight
}

ここでもフロントエンドの小ネタです🤯。ドキュメントのoffsetHeightoffsetWidth読み取ることで、ドキュメントのリフローがトリガーされます。これを理解するためには、レンダリングキューに変更があるのにリフローせずに直接ドキュメントの幅や高さを取得すると、取得する要素の幅や高さが古い情報になる可能性があるため、ブラウザは読み取り前にドキュメントをリフローする必要があるということです。

その後の作業は非常に簡単です。要素に[name]-moveクラスを追加し、以前に追加したtransitionDurationTransformプロパティを削除するだけです。要素は自然に新しい位置にスムーズに戻るでしょう〜transitionendイベントを監視して (L83-L93)、後処理を行います(遷移に関連するクラス名の削除など)。

ここまで読んだら、最初の問題を解決することができるようになりました。遷移効果を実現するには、要素が古い位置に正しく配置されていることを確認する必要があります。Vue では、要素がTransformを使用して古い位置に配置されることを保証するために、遷移時間を 0 秒に設定し、強制的なリフローを行っています。しかし、人為的に追加された高優先度のtransitionプロパティにより、リフロー時に要素がすぐに古い位置に戻らないため、遷移効果が失われてしまいます。

私は TransitionGroup の関係ないコードを簡略化したデモも作成しましたので、興味があればご覧ください:https://codepen.io/DaiDR/pen/VwdMRxa

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。