Houdini のアニメーション ワークレット

ウェブアプリのアニメーションを強化

要約: アニメーション ワークレットを使用すると、命令型のアニメーションを記述し、 デバイスのネイティブ フレームレートで撮影されるため、 メインスレッドのジャンクに対するアニメーションの復元性を高め、リンク可能な状態にします。 時間の代わりにスクロールします。アニメーション ワークレットは Chrome Canary 版( "ウェブ プラットフォームの試験運用版機能"フラグ)、Chrome 71 のオリジン トライアルを予定しています。そのまま使用を開始できます 本日より段階的に強化されます。

別のアニメーション API はどうでしょうか。

いいえ。それは、すでにある機能を拡張したもので、もっともな理由があります。 初めから始めましょう。ウェブ上の DOM 要素をアニメーション化する場合 現在は 2 1⁄2 の選択肢があります。CSS 遷移 シンプルな A から B への遷移、CSS アニメーション 周期的な、より複雑な時間ベースのアニメーションと Web Animations API (WAAPI)を利用できます。WAAPI のサポート マトリックスはかなり厳しく見えますが、 処理中ですそれまでに polyfill

これらのメソッドに共通するのは、ステートレスであり、 時間主導の意思決定です。デベロッパーが試してみた効果の中には ステートレス アプリケーションに適しています。たとえば悪名高いパララックス スクローラーは、 スクロールドリブンであることがわかります。現在、ウェブ上で高性能なパララックス スクローラーを実装するのは驚くほど困難です。

ステートレスについてはどうでしょうか。Android の Chrome のアドレスバーです 例です。下にスクロールすると、ビューの外にスクロールします。しかし、 上へスクロールすると 途中で止まっていても そのページの下部に移動しますアニメーションはスクロール位置だけでなく 前のスクロール方向を変更できます。ステートフルである。

もう一つの問題は、スクロールバーのスタイル設定です。スタイルが良くないことはよく知られています。 スタイリングが十分ではありませんスクロールバーにヤンネコを表示するにはどうすればよいですか? どの手法を使用する場合でも、カスタムのスクロールバーを作成することで、 簡単でもありません。

要点は、これらすべては気まずく、不可能にはならないことです。 効率的に実装できますそのほとんどはイベントや requestAnimationFrame: 画面があっても 60 fps を維持できます 90 fps または 120 fps 以上で動作し、 貴重なメインスレッド フレーム バジェットを

アニメーション ワークレットは、ウェブのアニメーション スタックの機能を拡張して、 簡単に作成できます。本題に入る前に ご覧ください

アニメーションとタイムラインの基礎知識

WAAPI とアニメーション ワークレットは、タイムラインを幅広く活用して、 好みの方法でアニメーションと効果をオーケストレートできます。このセクションの内容 タイムラインの概要や、アニメーションでの仕組みについて簡単に説明します。

各ドキュメントには document.timeline があります。ドキュメントがアップロードされると 0 から始まり、 ドキュメントが作成されてからのミリ秒数がカウントされます。次のすべて このタイムラインを基準としてドキュメントのアニメーションが機能します。

もう少し具体的なために、この WAAPI スニペットを見てみましょう。

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

animation.play() を呼び出すと、アニメーションはタイムラインの currentTime を使用します。 指定します。アニメーションの遅延は 3, 000 ミリ秒です。つまり、 タイムラインが `startTime に達すると、アニメーションが開始(または「アクティブ」)になります

  • 3,000. After that time, the animation engine will animate the given element from the first keyframe (translateX(0)), through all intermediate keyframes (translateX(500px)) all the way to the last keyframe (translateY(500px)) in exactly 2000ms, as prescribed by thedurationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3000 + 1000and the last keyframe atstartTime + 3000 + 2000`。ポイントは、 タイムラインはアニメーションの位置をコントロールします。

アニメーションが最後のキーフレームに達すると、最初のキーフレームにジャンプします。 アニメーションの次の反復処理を開始します。このプロセスでは iterations: 3 を設定してから合計 3 回です。アニメーションが 停止しない場合は、iterations: Number.POSITIVE_INFINITY と記述します。こちらが コードの結果 ご覧ください。

WAAPI は非常に強力であり、 イージング、開始オフセット、キーフレームの重み付け、塗りつぶし動作など、 説明します。詳しくは、CSS トリックでの CSS アニメーションに関する記事をご覧ください。

アニメーション ワークレットの作成

タイムラインの概念を理解したところで、次は アニメーション ワークレットと、それを使ってタイムラインを操作する方法アニメーション Worklet API は、WAAPI をベースにしているだけでなく、拡張可能なウェブという意味で、より下位のプリミティブであり、 WAAPI の仕組みについて説明します。構文に関しては、非常によく似ています。

アニメーション ワークレット WAAPI
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

違いは、最初のパラメータ(ワークレットの名前)にあります。 決めます。

機能検出

この機能を搭載した最初のブラウザは Chrome です。ブラウザが AnimationWorklet が存在することを想定したコードではありません。コンテナを読み込む前に ユーザーのブラウザがサポートしているかどうかを検出し、 シンプルなチェックで AnimationWorklet を実行します。

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

ワークレットの読み込み

ワークレットは Houdini タスクフォースによって導入された新しい概念で、 構築とスケーリングが容易になりますワークレットの詳細については、このモジュールで 後ほど説明しますが、わかりやすくするために 軽量のスレッド(ワーカーなど)を使用することです。

「passthrough」という名前のワークレットを を指定します。

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

ここでは何が起きているのでしょうか。クラスをアニメーターとして登録するために、 AnimationWorklet の registerAnimator() 呼び出し(「passthrough」という名前が付けられています)。 これは、上記の WorkletAnimation() コンストラクタで使用した名前と同じです。完了後 登録が完了すると、addModule() によって返される Promise が解決され、 このワークレットを使用してアニメーションの作成を開始できます。

インスタンスの animate() メソッドは、フレームごとに ブラウザがレンダリングを要求し、アニメーションのタイムラインの currentTime を渡す 現在処理中の影響を確認できます1 つだけです KeyframeEffect です。currentTime を使用して効果の localTime であるため、このアニメーターが「パススルー」と呼ばれる理由です。このコードを使用して ワークレット、WAAPI、上記の AnimationWorklet は、 ご覧のとおり、 demo

時間

currentTime メソッドのanimate()パラメータは、currentTime WorkletAnimation() コンストラクタに渡したタイムライン。前の その時間を関数に渡しましたしかし、これは JavaScript コードを使うと、時間を歪められます 💫?

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

currentTimeMath.sin() を取得し、その値を 範囲は [0;効果が定義されている時間範囲です。現在 アニメーションは大きく異なるように見えますが、 キーフレームやアニメーションのオプションを変更したワークレット コードは、 適用する効果をプログラムで定義できます。 どの順序で、どの程度再生されるかを 確認できます

「オプション」ではなく「オプション」を選択

ワークレットを再利用して、その番号を変更したい場合があります。このため、 WorkletAnimation コンストラクタを使用すると、ワークレットにオプション オブジェクトを渡すことができます。

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

このでは、 どちらのアニメーションも同じコードで行いますが、オプションは異なります。

地元の州を表示して!

前にも触れましたが、アニメーション ワークレットが解決しようとしている主な問題の一つは、 ステートフルなアニメーションを提供しますアニメーション ワークレットで状態を保持できます。ただし、 ワークレットのコア機能の一つは、別のワークレットや リソースを節約するためにさらに破棄される場合もあります。その場合、 あります。状態の喪失を防ぐために、アニメーション ワークレットには、 ワークレットが破棄される前に呼び出され、状態を返すのに使用できます。 渡されます。ワークレットが呼び出されたときに、そのオブジェクトはコンストラクタに渡されます。 構成されます。初回作成時には、このパラメータは undefined になります。

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

こちらのデモを更新すると、 正方形が回転する方向の可能性。ブラウザが破棄された場合や 別のスレッドに移行すると、別のスレッドが 作成時に Math.random() を呼び出すため、 方向です。これを回避するために、 ランダムに選択された方向を state として選択し、コンストラクタでそれを使用します(指定されている場合)。

時空を追う: ScrollTimeline

前のセクションで説明したように、AnimationWorklet では、 タイムラインの進行が影響にどう影響するかをプログラムで定義する 作成します。これまでのタイムラインは常に document.timeline です。 時間を記録します。

ScrollTimeline は新たな可能性を切り開き、アニメーションを推進できます 目を向けます今回は最初のモジュールを再利用します 「パススルー」そのためのワークレット demo:

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

document.timeline を渡す代わりに、新しい ScrollTimeline を作成します。 ご想像のとおり、ScrollTimeline は時間を使用しませんが、 ワークレットに currentTime を設定する scrollSource のスクロール位置。参加中 最上部(または左)までスクロールした場合は currentTime = 0 を意味しますが、 一番下(または右)までスクロールすると、currentTime が以下に設定されます。 timeRange。このボックスをスクロールして デモでは、 赤色のボックスの位置を調整します

スクロールしない要素で ScrollTimeline を作成すると、 タイムラインの currentTimeNaN になります。特にレスポンシブデザインでは currentTime として NaN を使用する準備ができている必要があります。多くの場合、 デフォルト値として 0 を設定できます。

アニメーションとスクロール位置をリンクすることは、以前から求められていたものです。 しかし、このレベルの忠実度で実際に達成されたことはありません(ハッキング いくつかあります。アニメーション ワークレットでは、これらのエフェクトを 簡単に実装できると同時に パフォーマンスも優れています次に例を示します。 視差スクロール効果を使用すると demo で、 数行でスクロールドリブン アニメーションを定義できるようになりました。

詳細

ワークレット

ワークレットは、分離されたスコープと非常に小さな API を持つ JavaScript コンテキストです あります。API サーフェスが小さいため、 パフォーマンスが向上します。また、ワークレットは、 特定のイベントループに振り分けられますが、必要に応じてスレッド間を移動できます。これは、 AnimationWorklet では特に重要です。

コンポジタ NSync

ご存じかもしれませんが、CSS プロパティには、アニメーションが高速なものもあれば、 できません。プロパティによっては、アニメーション化するために GPU でなんらかの処理が必要となる場合もあれば、 ブラウザによるドキュメント全体の再レイアウト。

Chrome には(他の多くのブラウザと同様に)、コンポジタと呼ばれるプロセスがあります。 レイヤの配置や処理のしやすさを GPU を利用して画面を定期的に更新したり、 画面の更新が可能な速さ(通常は 60 Hz)が理想的です。どの Pod が CSS プロパティはアニメーション化されているため、ブラウザには必ず 他のプロパティはレイアウトを実行する必要があります。 メインスレッドでしか実行できないオペレーションです。使用するプロパティによって、 アニメーションを予定している場合、アニメーション ワークレットは コンポジタと同期して別のスレッドで実行できます。

手首をたたく

通常、共有可能なコンポジタ プロセスは 1 つだけです。 GPU は競合の激しいリソースであるため、複数のタブを複数のタブに分けて実行することができます。コンポジタが なんらかの理由でブロックされると、ブラウザ全体が停止し、 できます。このような状況は絶対に避ける必要があります。では、 フレームが読み込まれるまでにコンポジタが必要とするデータをワークレットが 表示されますか?

このような場合、ワークレットは仕様に従って「スリップ」できます。遅れている コンポジタは最後のフレームのデータを再利用して フレームレートを上げることができます。視覚的にはジャンクのように見えますが、 ブラウザがユーザー入力に反応する点です。

まとめ

AnimationWorklet には多くの側面があり、ウェブにもたらすメリットがあります。 明らかなメリットは、アニメーションをより細かく制御できることと、 アニメーションを使用して、これまでにないレベルの視覚的な再現性をウェブにもたらします。API は 設計により、ジャンクに対するアプリの耐性を高めると同時に、 同時にすべての新機能を利用できます。

アニメーション ワークレットは Canary 版です。次の要素を使用したオリジン トライアルを目指しています。 Chrome 71。新しいウェブ体験をぜひご活用いただき、ご意見やご感想をお待ちしています 改善点についてお話ししますまた、 ポリフィル 同じ API を提供しますが、パフォーマンスが分離されません。

なお、CSS 遷移と CSS アニメーションは引き続き有効です。 基本的なアニメーションの場合ははるかにシンプルにできます。しかし、必要に応じて AnimationWorklet があなたをサポートしています。