HTMLでPixi.js

![20200310cover.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636/7d783918-7f43-77e0-06e2-b363de3f6ea5.png)

[Pixi.js](https://www.pixijs.com/) とは、ブラウザ上で動作するWebGLを用いた2Dグラフィックスライブラリです。

Pixi.js は、JavaScript から使用するライブラリで、以下のようなプログラムを書くことで使用できます。

```js
const app = new PIXI.Application()

document.body.appendChild(app.view)

// スプライトを追加
const sprite = PIXI.Sprite.from("hoge.png")
app.stage.addChild(sprite)
```

上記の例では、Pixi.js の初期化と、スプライト（画像）の表示を行っています。`PIXI.Sprite.from`で新たなスプライトのオブジェクトを生成し、それを`app.stage`に対して`addChild`で追加することで、画像の表示が行われる、という例です。

Pixi.js は、表示物の情報を階層構造で保持し、その情報に従って画面の表示を行います。これは、HTMLの構造とよく似ています。

そこで、Custom Element の機能を用いれば、HTML で Pixi.js の表示物を宣言的に定義して Pixi.js による画面表示が行えるのではないかと考えて、試してみました。

# Custom Element を使った要素の定義

まずは、`PIXI.Application`を指す`<pixi-app>`という要素を定義してみます。とりあえず動作させられることだけを確認したいため、内部の実装は雑です。

```js
customElements.define("pixi-app", class PixiApplication extends HTMLElement {
 constructor() {
   super()
   this.pixiObj = new PIXI.Application()
   const shadow = this.attachShadow({mode: "open"})
   
   shadow.appendChild(this.pixiObj.view)
   
   const style = document.createElement("style")
   style.textContent = `
     :host {
       display: inline-block;
     }
   `
   shadow.appendChild(style)
 }
})
```

Shadow DOM で、内部に Pixi.js の`<canvas>`要素を追加しています。

次に、画像リソースを表す`PIXI.Texture`の要素として`<pixi-texture>`を定義します。

```js
customElements.define("pixi-texture", class PixiTexture extends HTMLElement {
 constructor() {
   super()
   const pixiApp = getPixiApp(this)
   if (pixiApp === null) {
     return
   }
   const src = this.getAttribute("src")
   this.pixiObj = new PIXI.Texture.from(src)
 }
})
```

次に、画像の表示を行う要素である`PIXI.Sprite`の要素として`<pixi-sprite>`を定義します。親要素から`<pixi-app>`要素を探す関数として`getPixiApp()`関数も定義して使用しています。

```js
const getPixiApp = (elem) => {
 let node = elem.parentElement
 while (node !== null) {
   if (node.tagName === "PIXI-APP") {
     return node
   }
   node = node.parentElement
 }
 return null
}

customElements.define("pixi-sprite", class PixiSprite extends HTMLElement {
 constructor() {
   super()
   const pixiApp = getPixiApp(this)
   const pixiTexture = pixiApp.querySelector(`#${this.getAttribute("texture")}`)
   this.pixiObj = new PIXI.Sprite(pixiTexture.pixiObj)
   
   const parentPixiObj = this.parentElement.pixiObj
   if (parentPixiObj instanceof PIXI.Application) {
     parentPixiObj.stage.addChild(this.pixiObj)
   } else if (parentPixiObj instanceof PIXI.Container) {
     parentPixiObj.addChild(this.pixiObj)
   }
 }
})
```

`<pixi-sprite>`の`texture`属性の値は、表示の対象としたい`<pixi-texture>`要素の`id`属性の値が設定される想定です。

これで、とりあえず何らかの表示を行うための最小限の要素の定義ができました。

実際に使用した例は以下のようになります。

```html
<pixi-app>
 <pixi-texture id="bunny" src="bunny.png"></pixi-texture>
 <pixi-sprite texture="bunny"></pixi-sprite>
</pixi-app>
```

`<pixi-app>`の子要素として`<pixi-texture>`、`<pixi-sprite>`を追加する形を想定しています。

とりあえず表示だけはされるのかの実験をするために、`PIXI.Sprite`の各プロパティを操作するための仕組みの実装は省略しました。

# 定義した要素を使用する

動作を試しますと、以下のように黒い`<canvas>`の左上に小さな画像が表示されます。

![pixi-element-debug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636/96bdf157-be89-b310-7614-899d48533e86.png)

うまくできました。

この Pixi.js の要素を HTML の要素として記述できる仕組みが本格的に実装できれば（されれば）、[React](https://ja.reactjs.org/)、[Preact](https://preactjs.com/)、[Superfine](https://github.com/jorgebucaran/superfine) などの仮想DOMの仕組みを持ったフレームワークを用いて Pixi.js プログラミングができそうです。

すでに React のコンポーネントで Pixi.js の要素を扱えるようにするライブラリとして [React PIXI](https://reactpixi.org/) というものがありますが、こちらは React 限定になります。