🖼️

canvasで激軽アイキャッチジェネレーターを作成する

2021/10/31に公開

成果物

canvasって何?

基本的なcanvasの扱い方

  • 絶対座標で物体を配置していくだけ
  • 再描画するときは全消しからのすべて再配置

実装

ざっくり流れ

  • フォームとjs部のデータやり取りはuseStateを利用
  • useEffectを利用してフォームへの入力を検知し、フォームに入力されたらするように再描画の関数を呼ぶ

canvasの部分に関してだけ解説

まずすべてクリア

  • canvasのwidth/heightは実際に表示されているcanvasの要素から取得
const canvas: NullableHTMLCanvasElement = getCanvasRef();
if(!canvas) return 

ctx.clearRect(0, 0, canvas.width, canvas.height);

画像を描画

  • drawImageを使って、canvasに画像をはりつけ。画像の左上を左上にあわせたいので0, 0を設定。
if (outputImage !== null) {
 ctx.drawImage(outputImage, 0, 0);
}

文字を設定

  • 改行を反映したいので、改行文字を読み取って分割。
  • 1行ごとに処理をしていく。
ctx.font = `${fontSize}px serif`
const splitedText = text.split("\n")
splitedText.forEach((target, index) => {
  <<中略>>
})

文字の後ろのマスクの大きさを計算・描画(forEachの中部分)

  • 描画順で出るので、先に文字の下のマスクを描画。
  • 高さはMDNを参考にちょっと不思議なとりかたをする。
  • fillStyleで色を設定しておく。文字の色も同じ方法で指定する。
  • 矩形はctx.fillRect(x, y, width, height)で指定できるので、文字に対してうまい位置になるようにする。今回は複数行対応したいので、タテのY座標は行数にあわせて色々追加。
  • 今回は矩形と文字の間のmarginもよしなになるように計算式に追加。
const measure = ctx.measureText(target)

var textWidth = measure.width;
var textHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent

if (target.length !== 0) {
ctx.fillStyle = rgbaFormatter({...backgroundRgb, alpha: backgroundOpacity})
ctx.fillRect(
  (canvasWidth - textWidth) / 2 - xMargin,
  (canvasHeight - textHeight) / 2 - yMargin + (textHeight + yMargin * 2) * (index - 1),
  textWidth + xMargin * 2,
  textHeight + yMargin * 2 + 2
);
}

文字の位置を計算・描画(forEachの中部分)

  • 文字サイズは上で計算したので、ここでは位置を計算して描画するのみ。
ctx.fillStyle = rgbaFormatter({ ...textRgb, alpha: textOpacity })
ctx.fillText(
target,
(canvasWidth - textWidth) / 2,
(canvasHeight / 2) + textHeight / 2 + (textHeight + yMargin * 2) * (index - 1)
);

以上

  • 以上を入力のたびに発生させたいので、useEffectで一連の流れを発生させる。

おわりに

  • XY座標とタテヨコの長さを組み合わせるだけなのでHTMLの要素の配置よりはずっとシンプルで簡単。
  • 動作としても軽くてサクサクですごいいい。コードとして全消し再描画ってどうなのと思ってたけど、全くチラつきがないし色々値変えても重くなったりしないので最高。

Discussion