スムーズな開始と終了のアニメーションを実現する 4 つの新しい CSS 機能

モーションはデジタル エクスペリエンスの中核となる要素であり、ユーザーをあるインタラクションから次のインタラクションへと導くことができます。ただし、ウェブ プラットフォームでのスムーズなアニメーションにはギャップがいくつかあります。たとえば、開始アニメーションと終了アニメーションを簡単にアニメーション化したり、ダイアログやポップオーバーなどの閉じることができない要素を最上位レイヤとの間でスムーズにアニメーション化したりできます。

こうしたギャップを埋めるため、Chrome 116 と 117 では、4 つの新しいウェブ プラットフォーム機能を追加し、個別のプロパティに対するスムーズなアニメーションと遷移を可能にします。

新機能には次の 4 つが含まれます。

  • キーフレーム タイムラインで displaycontent-visibility をアニメーション化できるようになりました(Chrome 116 以降)。
  • transition-behavior プロパティで allow-discrete キーワードを指定すると、display のような個別のプロパティの移行が有効になります(Chrome 117 以降)。
  • @starting-style ルール: display: none から最上位レイヤへのエントリ エフェクトをアニメーション化します(Chrome 117 以降)。
  • アニメーション中の最上位レイヤの動作を制御する overlay プロパティ(Chrome 117 以降)。 ## キーフレームにアニメーションを表示する

Chrome 116 以降では、キーフレーム ルールで displaycontent-visibility を使用できます。これらは、キーフレームが発生したときに入れ替えられます。これをサポートするために追加の値を追加する必要はありません。

.card {
  animation: fade-out 0.5s forwards;
}

@keyframes fade-out {
  100% {
    opacity: 0;
    display: none;
  }
}

上記の例では、0.5 秒の期間にわたって不透明度を 0 にアニメーション化し、display を none に設定します。さらに、forwards キーワードにより、アニメーションが終了状態にとどまるため、適用される要素が display: noneopacity: 0 のままになります。

これは、遷移でできることを模倣したシンプルな例です(移行のセクションのデモをご覧ください)。ただし、遷移では、次の例のように、より複雑なアニメーションを作成することはできません。

.card {
  animation: spin-and-delete 1s ease-in forwards;
}

@keyframes spin-and-delete {
  0% {
    transform: rotateY(0);
    filter: hue-rotate(0);
  }
  80% {
    transform: rotateY(360deg);
    filter: hue-rotate(180deg);
    opacity: 1;
  }
  100% {
    opacity: 0;
    display: none;
  }
}

spin-and-delete アニメーションは終了アニメーションです。まず、カードが y 軸上で回転し、色相回転が行われ、80% でタイムラインに沿って不透明度が 1 から 0 に移行します。最後に、カードは display: block から display: none に切り替わります。

終了アニメーションの場合は、要素に直接適用するのではなく、アニメーションのトリガーを設定できます。たとえば、アニメーションを適用するクラスをトリガーするボタンにイベント リスナーをアタッチすると、次のようになります。

.spin-out {
   animation: spin-and-delete 1s ease-in forwards;
}
document.querySelector('.delete-btn').addEventListener('click', () => {
 document.querySelector('.card').classList.add('spin-out');
})

上記の例の終了状態は display:none です。さらに進んで、タイムアウトを設定して DOM ノードを削除し、アニメーションが最初に終了することが必要なケースは数多くあります。

個別アニメーションの移行

キーフレームで個別のプロパティをアニメーション化する場合とは異なり、個別のプロパティを遷移するには、allow-discrete 遷移動作モードを使用する必要があります。

transition-behavior プロパティ

allow-discrete モードは個別の遷移を可能にするもので、transition-behavior プロパティの値です。transition-behavior は、normalallow-discrete の 2 つの値を受け入れます。

.card {
  transition: opacity 0.25s, display 0.25s;
  transition-behavior: allow-discrete; /* Note: be sure to write this after the shorthand */
}

.card.fade-out {
  opacity: 0;
  display: none;
}
注: この移行デモでは最初のアニメーション デモとは異なる手法を使用していますが、見た目は似ています。

transition の省略形でもこの値を設定するため、このプロパティを省略し、代わりに各遷移の transition の省略形の最後に allow-discrete キーワードを使用できます。

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete;
}

.card.fade-out {
  opacity: 0;
  display: none;
}

複数の個別のプロパティをアニメーション化する場合は、アニメーション化するプロパティごとに allow-discrete を追加する必要があります。次に例を示します。

.card {
  transition: opacity 0.5s, display 0.5s allow-discrete, overlay 0.5s allow-discrete;
}

.card.fade-out {
  opacity: 0;
  display: none;
}

エントリ アニメーションの @starting-style ルール

ここまで、終了アニメーションについて説明してきました。そこで、開始アニメーションを作成するには @starting-style ルールを使用する必要があります。

@starting-style を使用して、ページ上で要素が開く前にブラウザが検索できるスタイルを適用します。これが「開く前」の状態です(アニメーションを開始する位置)。

/*  0. BEFORE-OPEN STATE   */
/*  Starting point for the transition */
@starting-style {
  .item {
    opacity: 0;
    height: 0;
  }
}

/*  1. IS-OPEN STATE   */
/*  The state at which the element is open + transition logic */
.item {
  height: 3rem;
  display: grid;
  overflow: hidden;
  transition: opacity 0.5s, transform 0.5s, height 0.5s, display 0.5s allow-discrete;
}

/*  2. EXITING STATE   */
/*  While it is deleting, before DOM removal in JS, apply this
    transformation for height, opacity, and a transform which
    skews the element and moves it to the left before setting
    it to display: none */
.is-deleting {
  opacity: 0;
  height: 0;
  display: none;
  transform: skewX(50deg) translateX(-25vw);
}

これで、TODO リストアイテムの開始状態と終了状態の両方になりました。

最上位レイヤとの間でアニメーション化する要素のアニメーション

最上位レイヤとの間で要素をアニメーション化するには、@starting-style を「開いた」状態で指定して、アニメーションの起点をブラウザに伝えます。ダイアログの場合、開いた状態は [open] 属性で定義されます。ポップオーバーには :popover-open 疑似クラスを使用します。

ダイアログの簡単な例を以下に示します。

/*   0. BEFORE-OPEN STATE   */
@starting-style {
  dialog[open] {
    translate: 0 100vh;
  }
}

/*   1. IS-OPEN STATE   */
dialog[open] {
  translate: 0 0;
}

/*   2. EXIT STATE   */
dialog {
  transition: translate 0.7s ease-out, overlay 0.7s ease-out allow-discrete, display 0.7s ease-out allow-discrete;
  translate: 0 100vh;
}

次の例では、開始時と終了時のエフェクトが異なります。ビューポートの下から上にアニメーション化して開始し、効果を終了してビューポートの上側に移動します。また、より視覚的にカプセル化するために、ネストされた CSS で記述されています。

ポップオーバーをアニメーション化する場合は、前に使用した open 属性の代わりに :popover-open 疑似クラスを使用します。

.settings-popover {
  &:popover-open {
    /*  0. BEFORE-OPEN STATE  */
    /*  Initial state for what we're animating *in* from, 
        in this case: goes from lower (y + 20px) to center  */
    @starting-style {
      transform: translateY(20px);
      opacity: 0;
    }
    
    /*  1. IS-OPEN STATE  */
    /*  state when popover is open, BOTH:
        what we're transitioning *in* to 
        and transitioning *out* from */
    transform: translateY(0);
    opacity: 1;
  }
  
  /*  2. EXIT STATE  */
  /*  Initial state for what we're animating *out* to , 
      in this case: goes from center to (y - 50px) higher */
  transform: translateY(-50px);
  opacity: 0;
  
  /*  Enumerate transitioning properties, 
      including display and allow-discrete mode */
  transition: transform 0.5s, opacity 0.5s, display 0.5s allow-discrete;
}

overlay 件の宿泊施設

最後に、最上位レイヤから popover または dialog をフェードアウトするために、遷移のリストに overlay プロパティを追加します。popoverdialog は祖先クリップと変換をエスケープし、コンテンツを最上位のレイヤに配置します。overlay を遷移しない場合、要素は即座にクリップ、変換、カバーアップの状態に戻り、遷移は起こりません。

[open] {
  transition: opacity 1s, display 1s allow-discrete;
}

代わりに、遷移またはアニメーションに overlay を含めて、overlay を他の機能とともにアニメーション化し、アニメーション化するときに最上位レイヤに留まるようにします。かなりスムーズに表示されます。

[open] {
  transition: opacity 1s, display 1s allow-discrete, overlay 1s allow-discrete;
}

また、最上位レイヤで複数の要素が開いている場合、オーバーレイを使用すると、最上位レイヤへの出入りのスムーズな遷移をコントロールできます。こちらの簡単な例をご覧ください。2 番目のポップオーバーを遷移するときに、overlay を 2 つ目のポップオーバーに適用しない場合は、遷移を開始する前に、まず最上位レイヤから移動し、他のポップオーバーの後ろに飛び出します。ただし、これは必ずしもスムーズな処理ではありません。

ビュー遷移に関する注意事項

DOM の要素の追加や削除など、DOM の変更を行う場合、スムーズなアニメーションを実現するためのもう一つの優れたソリューションとして、ビュー遷移があります。ビュー遷移を使用して作成された上記の例のうちの 2 つを次に示します。

この最初のデモでは、@starting-style などの CSS 変換を設定するのではなく、ビュー遷移で遷移を処理します。ビュー遷移は、次のように設定されています。

まず、CSS で、各カードに個別の view-transition-name を指定します。

.card-1 {
  view-transition-name: card-1;
}

.card-2 {
  view-transition-name: card-2;
}

/* etc. */

次に、JavaScript で、ビュー遷移で DOM ミューテーション(この場合はカードの削除)をラップします。

deleteBtn.addEventListener('click', () => {
  // Check for browser support
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      // DOM mutation
      card.remove();
    });
  } 
  // Alternative if no browser support
  else {
    card.remove();
  }
})

これで、各カードの新しい位置へのフェードアウトとモーフィングをブラウザで処理できるようになりました。

これは、リストアイテムの追加と削除のデモを使用すると便利です。この場合は、作成したカードごとに一意の view-transition-name を追加する必要があります。

まとめ

これらの新しいプラットフォーム機能により、ウェブ プラットフォームでスムーズな開始と終了のアニメーションを実現できるようになりました。詳しくは、以下のリンクをご覧ください。