Canvasを使って画像をテキストの形にくり抜く方法

まずは、完成形の画像とコードを貼ります。


```jsx
const canvas = document.querySelector('canvas.canvas')
const ctx = canvas.getContext('2d')
let width = window.innerWidth
let height = window.innerHeight
canvas.width = width
canvas.height = height

let image = new Image()
image.src = 'https://drive.google.com/uc?export=view&id=1Pp6a8w8ChESp4vYF_jhYUfNiidIEtaHT'

image.onload = () => {
  draw()
}

const draw = () => {
	const scale = Math.max(width / image.width, height / image.height);
	const ratioX = (width / 2) - (image.width / 2) * scale;
	const ratioY = (height / 2) - (image.height / 2) * scale;
	
	ctx.drawImage(image, ratioX, ratioY, image.width * scale, image.height * scale);
	
	ctx.globalCompositeOperation = 'destination-in';
	const text = "Text Clip Sample"
	ctx.font = `bold 100px Helvetica Neue`;
	const textWidth = ctx.measureText(text).width;
	
	ctx.fillText(text, ((width - textWidth) / 2), height / 2);
}

window.addEventListener('resize', () => {
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	width = window.innerWidth
	height = window.innerHeight
	canvas.width = window.innerWidth
	canvas.height = window.innerHeight
	image = new Image()
	image.src = 'https://drive.google.com/uc?export=view&id=1Pp6a8w8ChESp4vYF_jhYUfNiidIEtaHT'
	image.onload = () => {
		draw()
	}
})
```

![スクリーンショット 2021-12-19 15.09.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704193/924561a2-123c-6b66-3a8c-658a3d7996f6.png)

# ① 初期設定

HTMLに書いたcanvasタグから、`getContext()`を使って2Dグラフィック描画のためのメソッドやプロパティを取得します。

```html
<canvas class="canvas"></canvas>
```

```jsx
const canvas = document.querySelector('canvas.canvas')
const ctx = canvas.getContext('2d')
```

次にcanvasのサイズを設定します。
今回はwidth, height共に画面幅いっぱいになるようにします。

```jsx
let width = window.innerWidth
let height = window.innerHeight

canvas.width = width
canvas.height = height
```

次に背景に使用する画像を読み込みます。
image要素を作成し、srcに画像のパスを設定します。

```jsx
let image = new Image()
image.src = '画像パス'
```

画像を読み込んだ後に、canvasに描く処理を実行します。
後々リサイズの処理の際に使用するので、別の関数に切り出しておきます。

```jsx
image.onload = () => {
	draw()
}
```

# ② 画像を描画する

では、実際にcanvasに画像を描画させていきます。
先ほど読み込んだ画像を描画するには、`drawImage()`を使用します。
引数にx, y, width, heightなどを細かく指定できるのですが、アスペクト比を保ちつつcanvasのサイズいっぱいに拡大させる、CSSで言うところの `object-fit: cover;`になるように描画したいと思います。
縦と横それぞれ倍率を出し、大きい方をscaleにセットします。
描画をする座標はデフォルトでは(0,0)(画面左上)になっているので、それぞれ拡大した大きさ分X座標(ratioX)とY座標(ratioY)をずらします。

```jsx
const scale = Math.max(width / image.width, height / image.height);
const ratioX = (width / 2) - (image.width / 2) * scale;
const ratioY = (height / 2) - (image.height / 2) * scale;

ctx.drawImage(image, ratioX, ratioY, image.width * scale, image.height * scale);
```

![スクリーンショット 2021-12-19 14.53.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704193/acb41b4c-4114-5c38-6b47-11ef31ffe89f.png)

画像が描画されました。

# ③ テキストを描画する

次にテキストを画像の上から描画します。
画面中央にテキストを配置させたいので、画面幅からテキストの長さを引いた数字からX座標を割り出します。
textWidthはテキスト全体のwidthを取得しています。

```jsx

const text = "Text Clip Sample"
ctx.font = `bold 100px Helvetica Neue`;
const textWidth = ctx.measureText(text).width;
ctx.fillText(text, ((width - textWidth) / 2), height / 2);
```

![スクリーンショット 2021-12-19 15.08.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704193/cc27c471-0ca1-02cb-1dc2-88190e73d325.png)

テキストが描画されました。
ではいよいよ画像をテキストでClipさせます。
方法は色々あると思いますが、今回は`globalCompositeOperation`を指定することで実現したいと思います。
`globalCompositeOperation`とは、新たな図形を描くときに合成の種類やブレンドモードを指定できるプロパティです。

[https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation](https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)

たくさん種類がありますが、今回は`destination-in`　を使用します。

```jsx
ctx.globalCompositeOperation = 'destination-in';　 // 追加
ctx.font = ${fontSize}px Helvetica Neue;
const textWidth = ctx.measureText(text).width;
ctx.fillText(text, ((width - textWidth) / 2), centerY / translateY);
```

![スクリーンショット 2021-12-19 15.09.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704193/924561a2-123c-6b66-3a8c-658a3d7996f6.png)


いい感じになりました。

# ④ リサイズ処理
リサイズ時の処理もしておきます。
`clearRect()`　で領域内に描画されていたものをリセットし、もう一度描画しなおします。

```jsx
window.addEventListener('resize', () => {
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	width = window.innerWidth
	height = window.innerHeight
	canvas.width = window.innerWidth
	canvas.height = window.innerHeight
	image = new Image()
	image.src = '画像パス'
	image.onload = () => {
		draw()
	}
})
```

今回は背景画像に静止画を使用しましたが、動画を使ったり、スクロールに合わせて文字が大きくしたり、色々な表現に応用できそうですね。

codepenに完成形を載せていますので、よければ参考にしてください。
[https://codepen.io/bonji810/pen/YzxbxqW?editors=1011](https://codepen.io/bonji810/pen/YzxbxqW?editors=1011)