Next.js で PixiJS を使う
Next.js 固有のハマりどころ
PixiJS のようなクライアント側でしか動作しないものをそのまま動かそうとすると、SSRで self is undefined のようなエラーが出ます。これを回避するために、dynamic import を利用します。
code:tsx
const Index = ({ foo }) => {
const FooCanvas = dynamic(() => import(‘src/components/FooCanvas’), {
ssr: false
})
return (
<section>
<FooCanvas {…foo} />
</srction>
)
}
dynamic import はコンポーネントに対してのみ利用できるので、Pixiアプリケーション をコンポーネントとして吐き出します。
ReactPixi を使う場合
シンプルに使う分には何も問題は発生しないでしょう。
code:tsx
import BarSprite from ‘./sprites/BarSprite’
const FooCanvas = () => {
return (
<Stage width={300} height={300} options={{ backgroundAlpha: 0 }}>
<Container x={150} y={150}>
<BarSprite />
</Container>
</Stage>
)
}
ただし Sprite は必ず分離しないとApplication Provider エラーが出る、ローダーをインスタンス化するタイミングによってキャッシュの残り方が違う、本家と違って autoDensity がデフォルトでtrue、などさまざまな(そして情報の少ない)制約や違いがそれなりにあるので、生のPixiJSを使いたいケースも出てくると思います。
PixiJS を使う場合
PixiJSをそのまま使う場合は、refからDOMにアクセスしてPixiアプリを追加します。
code:tsx
// ts で使う場合は ’import PIXI from ‘pixi.js’ だとうまく動きません
// このように import しても tree-shaking は効いたはずです(1年半前の知識)
import * as PIXI from ‘pixi.js’
const FooCanvas = () => {
// ref が読まれたタイミングで Pixiアプリを追加します
const onElementLoaded = (element: HTMLDivElement) => {
if (!element) return
const app = new PIXI.Application(appOptions)
element.appendChild(app.view);
// 略
}
return <div ref={onElementLoaded} />
}
Canvas の中身を完全に隠蔽するならこれで実装できますし、理想的です。
Canvas の中身を一部だけリアクティブにする
現実的に、Canvasの外でのイベントによってステータスが変化し、それが Canvas のパラメータの一部を変更する、といったことは起こり得ます。こうなると苦しいのが再レンダリングの考慮です。ReactPixi ではSprite自体をコンポーネントとして分離しており、Spriteの中でprops にのみ依存する擬似useMemo を作ることで再描画を抑制しています。ですが個人的には、Next.js と PixiJS をなるべく疎にする方法を推します。
変更される箇所を分離して1つの canvas にする
出現位置が固定で、光源などの影響を受けないなら分離してしまうのが1番です。Canvasで凝って実装したUIコンポーネントなんかが当てはまります。
変更する側も Canvas に入れる
複雑なステートでないなら Next.js を介さないことも考えます。
どちらも無理
特殊な事情があっていずれも困難なら、下手に再生産するよりReactPixiを採用したほうがいいでしょう。