LitElementのDOM変化でView Transitions APIを使って簡単かつ綺麗な凡庸アニメーションを実装する方法

ハイサイ！オースティンやいびーん！

# 概要

ChromeとEdgeがサポートしている`View Transitions API`をLitと一緒に使うと、とても簡単に再レンダー時のスムーズなアニメーションができます。

# アニメーションを付ける部品

ChromiumベースのブラウザでLitの砂場を開きましょう。

https://lit.dev/playground/

この単純なデフォルトの部品にステート変更を加えて、再レンダーを起こすようにします。

```typescript 
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  static styles = css`p { color: blue }`;

  @property()
  name = 'World';
  
  firstUpdated() {
    setInterval(() => {
      const newName = this.name === 'World' ? 'Lit' : 'World';
      this.name = newName;
    }, 2000);
  }

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}
```

`firstUpdated`はLitが最初にDOMにレンダーした後に呼ばれるメソッドです。その時に2秒おきに`name`を変えるようにします。

## 結果

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/557dfb77-5b66-b6b5-6988-57f61a29d289.gif)

急に変わるようになっています。これをスムーズに変わるようにしましょう。

# View Transitions APIをLitのレンダー時に作動させる

Litのソースコードを調べてみると、`LitElement`が実際にDOMの変更を行なってくれる`'lit-html`.render`を呼んでくれるのは、`update`というメソッドの中です。

https://github.com/lit/lit/blob/main/packages/lit-element/src/lit-element.ts#L165

```typescript
  /**
   * Updates the element. This method reflects property values to attributes
   * and calls `render` to render DOM via lit-html. Setting properties inside
   * this method will *not* trigger another update.
   * @param changedProperties Map of changed properties with old values
   * @category updates
   */
  protected override update(changedProperties: PropertyValues) {
    // Setting properties in `render` should not trigger an update. Since
    // updates are allowed after super.update, it's important to call `render`
    // before that.
    const value = this.render();
    if (!this.hasUpdated) {
      this.renderOptions.isConnected = this.isConnected;
    }
    super.update(changedProperties);
    this.__childPart = render(value, this.renderRoot, this.renderOptions);
  }
```

これを自分らの部品で動作を変えたらView Transitions APIを再レンダーのたびに引き起こせそうです。

## updateをoverrideする

updateをoverrideして、元の関数を呼ぶようにしましょう。

```typescript
import {html, css, LitElement, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  static styles = css`p { color: blue }`;

  @property()
  name = 'World';
  
  firstUpdated() {
    setInterval(() => {
      const newName = this.name === 'World' ? 'Lit' : 'World';
      this.name = newName;
    }, 2000);
  }
  
  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
  
  override update(changed: PropertyValues) {
    const update = super.update.bind(this, changed);
    
    if (!('startViewTransition' in document)) {
      update();
      return;
    }

    // TypeScriptはまだstartViewTransitionを知らないようです
    (document as any).startViewTransition(update);
  }
}
```

`override update`では、呼びたい関数を`update`という中間変数に`bind`を使ってまとめ、View Transitions APIがサポートされていれば、作動するように呼んでいます。

サポートされていなければ、従来通り呼ぶだけで終わりです。

## 結果

![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/ae93fe4f-b244-09ef-6055-59487cc988ac.gif)

Gifなのでちょっと荒いかもしれませんが、とても滑らかにアニメートされています！

これでモーダルのアニメーションをほぼ何もせずに、CSSに触れずにできてしまいます！:innocent:

# まとめ

簡単に紹介しましたが、実に様々なアニメーション、それこそネットフリックスのようなアニメーションをとても簡単に実装できるようになったのです。

ブラウザのAPIなので、パフォーマンスもよく、バグも起きないのです。

このView Transition APIについて知りたければ、Chromeの記事をぜひお読みください。

https://developer.chrome.com/docs/web-platform/view-transitions/

Litじゃなくても使えます！