canvasを使うプロジェクトに参加していて、デバッグ用のためにページの描画後に一回きり、空白のcanvasに対して図形を追加する処理を、useEffect
により実行しようとしました。しかし、画面上には何も表示されず、数時間を溶かしました。
TL;DR
useEffect
上でcanvas操作をするには、レンダリングのタイミングと操作の順番に配慮する必要があることがわかった。
再現
期待したこと:とにかくなんでもいいから図形がcanvas上に描かれること
現実:何も表示されない
return <>
<canvas id='bar-canvas' />
</>
useEffect(()=>{
const canvas = document.getElementById('bar-canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d')
ctx.rect(10, 10, 150, 100);
ctx.fill();
}, [])
問題の特定
- console.logで
canvas
の中身をロギング- オブジェクト自体は出力されるので正しく定義されていることを確認
- Chrome DevToolsでタグがDOMに存在されることを確認
- CSSプロパティも特に非表示にしている、などは確認できなかった
- Canvasが何かに埋もれて隠れているのか?
-
useEffect
内にCanvas画像をダウンロードするコードを挿入し、出力された画像を確認 - 画像も空白だったので、この可能性はないと判断
-
setTimeout(
()=>{
const link = document.createElement('a');
link.download = 'filename.png';
link.href = (document.getElementById('bar-canvas') as HTMLCanvasElement ).toDataURL()
link.click();
}
, 3000)
- canvasをuseEffectで操作しているのが良くないのか?
- useEffect内でのcanvas操作のコードを
setTimeout
でラップして時間を置いてからcanvas操作がされるようにした - 無事、画面に図形が描画された
- useEffect内でのcanvas操作のコードを
問題の原因について考える
おそらくuseEffect
の描画のタイミングの問題なのか?
GoogleでuseEffect canvas
と検索すると、以下のSOの記事を発見した。
レンダリングのタイミングと、操作のタイミングの順番が大事なようだ。上の記事によると、これはuseEffect
自体の問題ではなく、元のJavaScript/HTML自体が持つ特性に過ぎないとのこと。Reactを使うにせよ、使わないにせよ、レンダリングとcanvas操作のタイミングには注意すべきということだそうだ。
とはいえ、Reactではどう解決したらいいのか、ということは上の記事に書いているので、そちらを参照されたい。
自分の場合の解決法
自分の場合、useEffect
を使っていたのは、デバッグのためだけであった。そもそもuseEffect
で図形を表示するのではなく、特定のイベントがあったときに図形を表示する、ということが自分のやりたいことであって、そのイベントはコンポーネントの描画時から時間が経ってから発生するので、当面必要に迫られるまで解決しないことにしてuseEffectを削除する、というのが、解決法になった。
わからないときにどうすればいいのか?
経験上、何がわからないのがわからない、というときは、そもそも何らかのレイヤーの知識がごそっと抜けていることが多い。今回の例でいえば、useEffectとレンダリングのタイミングの関係や、canvasとレンダリングの関係などの知識が全くなかったので、そこに問題が生じていたという考え自体に至らなかった。
一つの視点:どんな仮説が立てられるか?
これは良くあるアプローチですが、これがうまく行くかどうかは、自分の知識に依存します。もし知識が初めからあるのであれば、仮説を立てて、順番に可能性を検証していくことが最短でしょう。しかし、そうでない場合には、全く先に進めないこともあります。
利用可能性ヒューリスティック
このような認知バイアスが人間にはあり、「利用可能性ヒューリスティック」と呼ばれています。自分に身近な知識が、可能性の列挙に影響してしまうということです。
では、どうすればいいのか、今回2時間以上浪費したので、自分なりに対策を考えました。それは、行き詰まったときには、自分の知識になるべく依存しないような視点を持つことです。
もう一つの視点:動いているコードから、自分が何を変更したのか?
今回は、 もともと動いていたコードの例を、自分でuseEffect内に挿入した、という差分がありました。 もしこのことを早く認識していれば、useEffect canvas
とGoogle検索して、最短で答えに辿り着いたかもしれません。特に、今回のような「エラーは出ないけど、期待通りに動かない」という、情報が少ない場合は、自分が何をしでかしたのか、を理解することが、唯一の情報源になる気がします。
行き詰まった時は、VSCodeの機能で差分でも眺めてみてはいかがでしょうか?
それでは、ごきげんよう!