はい。嘘です。
正しくは、PCが遅い原因がわかった。でした。顛末は最後に書いていますが、最初は、canvasの使い方から。
canvasの使い方
HTML
HTMLで下記のように書きます。
<canvas id="canvas" width="300" height="170"></canvas>
JavaScript
基本
cotnext
const c = document.getElementById('canvas'); const ctx = c.getContext('2d');
context を取得してから、描画をします。
ctx.beginPath()
beginPath をしないと、最後にいた座標から、線を描き出してしまう場合がありますが、これを呼んでおけば問題ないです。
stroke, fill
storke の場合は、線を描きますが、fillの場合は、中身を塗ります。
- strokeRect, fillRect
- stroke, fill
のように、対になっています。
座標
x= 0, y =0 は、左上になります。 最初に定義したcanvasだと、x=300, y = 170 が右下になります。
線を描く
書きたい場所に移動して、
ctx.moveTo(x, y); ctx.lineTo(x, y); ctx.stroke();
moveToで移動した、x, y から、lineToで指定した、x, y まで線を描画します。
最初に、moveToで、移動して、そこから、lineTo で線の終端をさし、stroke で線を描きます。
四角を描く
strokeRectを使います。
ctx.strokeRect(x, y, xの幅, yの幅)
円を描く
ctx.beginPath()。 ctx.arc( x, y, radius, startAngle, endAngle, counterclockwise) ; ctx.stroke();
radius は半径です。startAngleは開始角度で、endAngleは終了角度。counterclockwiseは、trueなら左回りに線を描きます。
startAngle = 0, endAngle = Math.PI * 2 であれば、真円が描けます。 半円であれば、endAngle = Math.PIです。
ctx.beginPath() をしないと、今いる座標から、円の開始点までの線も描画してしまうので、注意してください。
下向きの半円
ctx.beginPath();
ctx.arc( 10,10, 10, 0, Math.PI, false) ;
ctx.stroke();
下向きの1/4の円
ctx.beginPath();
ctx.arc( 10,10, 10, 0, Math.PI / 2, false) ;
ctx.stroke();
fill で中身を塗りつぶしたい場合は、
ctx.moveTo(10,10);
ctx.arc(10, 10, 10, 0, Math.PI / 2, false) ;
ctx.fill();
fill を使う場合、moveToで移動した座標から、円の開始点から終了点を結んだ部分を塗ります。
なので、下記のようにすると、
ctx.moveTo(90,90);
ctx.arc( 40,40, 50, 0, Math.PI / 2, false) ;
ctx.fill();
外側が塗りつぶされます。
描画したものを消す
clearRect を使います。strokeRectと使い方は同じです。
ctx.clearRect(x, y, xの幅, yの幅);
これで、指定した座標の位置から、x, y の領域を削除します。消す領域ですが、若干多めに取っておいたほうが良いかもしれません。
textを描く
ctx.fillText("テキスト", x, y, 幅);
x, y の座標から幅分の領域に、指定したテキストを描画します。
x, y の位置ですが、テキストの左下が、座標の位置です。
一度描いたものを削除する場合、clearRectで指定するのは、y - フォントサイズ + アルファ
くらいを指定するひつようがあります。 y の幅指定も、フォントサイズ+アルファくらいです。
フォントの指定は、下記のように行います。
ctx.font = '9px serif';
canvas の良いところ
プログラマブルに絵を描けるので、配置場所や、スケールも自由に変更できます。
下向きにに複数の円が描かれる。
const c = new MyCircle({id: 'canvas', x: 0, y: 0, scale: 1, direction: false}); c.draw();
上向きに複数の円が描かれる。
const c = new MyCircle({id: 'canvas', x: 0, y: 100, scale: 1, direction: true}); c.draw();
と行った感じにできます。
PCのボトルネックに気づいた理由
アニメーションをしようと思って、setTimeoutで、canvasに描画しまくっていたわけです。
先程のclassに、下記のようなメソッドを追加した感じですね(実際はもっと色々やってた)。
animate (max) { const ctx = this.ctx; max ||= 50; ctx.clearRect(0,0, this.canvas.width, this.canvas.height); const fn = (x) => { x ||= 0; ctx.beginPath(); ctx.arc( this.x + x, this.y + x * (this.direction ? -1 : 1), x, 0, Math.PI * 2, false); ctx.stroke(); if (x < max * this.scale) { setTimeout(() => {fn(x + 1)}, 1); } }; fn(); }
そうすると、描画がクソ重いのが、目に見えてわかるわけですね。
で、たまたま、外部ディスプレイが切れて、ノートPCの方の解像度が変更されたタイミングでスピードがかなり改善されました。
重い原因は何だったのか?
Ubuntuを使っているのですが、解像度の設定と、画面の拡大を指定することが出来ます。下記の赤ラインで引いたところです。
ここを調節していると、思った以上にパフォーマンスが落ちるようです。 Firefoxがまともに動かないレベルで重かったのですが、これを100%にしたら、普通に動きました。
というわけで、せっかくなので、canvasを使ったベンチマークを作りました。反復横飛びの片道で1とカウントしています。 (※遊びでつくっただけです。めっちゃ雑な計測です)
スペックは、下記な感じです。
外部ディスプレイ(3840x2160:100%) ノートPCのディスプレイ(3840x2160:100%)
外部ディスプレイ(3840x2160:100%) ノートPCのディスプレイ(1960x1200:100%)
外部ディスプレイ(3840x2160:100%) ノートPCのディスプレイ(3840x2160:125%)
外部ディスプレイ(3840x2160:125%) ノートPCのディスプレイ(3840x2160:125%)
外部ディスプレイ(3840x2160:150%) ノートPCのディスプレイ(3840x2160:125%)
外部ディスプレイ(3840x2160:125%) ノートPCのディスプレイ(1920x1200:100%)
外部ディスプレイ(3840x2160:125%) ノートPCのディスプレイ(3840x2160:100%)
結論
外部ディスプレイとノートPCのディスプレイをいずれかを拡大している場合に一番、パフォーマンスが劣化するようです。
なお、Windoows のSurface Pro 7 Core i7 1.3GHz だと、0.9/sec でした。スマホ(Xiaomi Readmi Note 10 Pro)も0.9/secでした。