【個人開発】プロ野球のおたくが勉強のためだけに作った応援タオル風画像ジェネレーター

HTML/CSSおよびJavaScriptの基礎の基礎を学習後、初めて制作したものについて書きます。

プロ野球チーム、オリックス・バファローズの公式販売グッズに「応援メッセージフェイスタオル」という商品があります。

↓本家。

https://shop.buffaloes.co.jp/_special/ouen_towel/

プリントメッセージにネット流行語やオタク用語を惜しみなくぶっ込んでくるユーモラスと、そのワードチョイスゆえの使い勝手の良さから登場以来絶大な人気を誇る看板グッズの一つとなっています。

このシリーズが好き過ぎるので、それ風の画像を作って遊べるサイトを制作しました。

# どんなサービスか
サイトはこちら

https://emuroku.github.io/buffaloes_towel_maker/

* よくあるロゴジェネレーター的なサイトです
* フォームに入力したテキストが画像に反映され、応援メッセージタオル風の画像を作成できます
* 作成したものは画像ファイルとしてダウンロードできます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974587/ec3b9508-d992-da11-bf59-94aea3e98b5b.png)


# 開発リポジトリ
https://github.com/emuroku/buffaloes_towel_maker

# 制作目的
* HTML / CSS / JavaScriptの基礎の復習
* 極小規模なものでいいのでとにかくサービスとして完成させて公開すること

# 使用した技術
### フロントエンド
* Canvas
    * JavaScriptで動的に図を描画できる要素
* JavaScript
    * canvasの操作
    * 入力フォームや作成ボタンなどのGUIの動作
    * 画像データ化とダウンロード
* Bootstrap4
    * html / cssのフレームワーク
* Google Fonts
    * テキスト出力用。本家のデザインに近いものを選定しました
  
### バックエンド
* なし！（通信要素ありません）

### ホスティング
* GitHub Pages
    * GitHubのリポジトリ上の静的ファイルを直接サイトとして公開できるホストサービス
https://docs.github.com/ja/pages/getting-started-with-github-pages/about-github-pages
    * 難しくて大変な本番デプロイ作業をしなくていいので、初めてのサイト公開にはうってつけでした。
# 開発にかかった期間
* プロトタイプに1w程度
* デバッグ、改修で3w程度
※1dあたり1～2hほど

# 実装内容
JavaScript/HTMLで大まかに以下の要素を実装しました
1. Canvasで指定のカラーのテンプレート画像を描画する
1. 受け付けたテキスト情報をcanvasで描画する
1. 作成物を画像データとしてダウンロードする
1. Twitterシェア用のデフォルトテキストと遷移ボタン配置

### 1. Canvasでテンプレート画像を描画する
```index.js
// ベース画像
var bgImg = new Image();
bgImg.src = 'メッセージ部分の下地画像'

var bgImg_l = new Image();
bgImg_l.src = 'チーム名部分の下地画像';

// --（中略）--

// canvasとcontext
const canvas_m = document.querySelector('#cv');
// message描画用
const ctx_m = canvas_m.getContext('2d');
// logo描画用
const ctx_l = canvas_m.getContext('2d');

// 背景画像読み込み後にcanvasへ描画
bgImg.onload = () => {
    if (basedrawed == false) {
        ctx_m.drawImage(bgImg, 0, 0);
        ctx_m.drawImage(bgImg, 0, 418, bgImg.width, bgImg_l.height, 0, 418, bgImg.width, bgImg_l.height);
    }
}
```
* `getContext()`メソッドを使い、メッセージ部分とチーム名部分にそれぞれのテキストを描画
* `onload`イベントでベースの画像を読み込み、`drawImage`メソッドで使用範囲を指定（ベース部分の画像サイズに合わせて各パラメータを設定）して表示
* ラジオボタンで選択されたカラーによりbgImgに入るアセットが変更されるようにしてあります
* `basedrawed`というフラグは、メッセージ部分とチーム名部分の入力を切り替えたときに毎度ベース画像を読み込み直し→入力済みの内容がクリアされてしまう　という現象を防ぐために追加しました（もっと賢いやり方があるかも）

※drawImageメソッドは以下を参考にさせていただきました

http://www.htmq.com/canvas/drawImage_s.shtml

### 2. 受け付けたテキスト情報をcanvasで描画する
```index.js
// 文字入力時に描画処理を呼び出す：メッセージ部分
textBox.addEventListener('input', () => {
    drawText(textBox.value);
    basedrawed = true;
})

//  文字入力時に描画処理を呼び出す：チーム名部分
logoBox.addEventListener('input', () => {
    drawText_logo(logoBox.value);
    basedrawed = true;
})

// --（中略）--

// タオル部分 描画処理
function drawText(text) {
    ctx_m.drawImage(bgImg, 0, 0, bgImg.width, bgImg.height - bgImg_l.height, 0, 0, bgImg.width, bgImg.height - bgImg_l.height); // ベース画像描画
    ctx_m.font = "bold 180px 'Kosugi Maru'"; // フォント指定
    ctx_m.textAlign = "center"; // 描画位置の左右中央揃え
    ctx_m.strokeStyle = font_color_main; ctx_m.lineWidth = 28; ctx_m.lineJoin = "round"; // 文字輪郭のスタイル指定
    ctx_m.fillStyle = "white"; // 文字内側の塗りつぶしのスタイル指定
    ctx_m.textBaseline = "middle"; // 描画位置の上下中央揃え
    ctx_m.strokeText(text, 640, 220, 1180); // 文字輪郭の指定
    ctx_m.fillText(text, 640, 220, 1180); // 文字内側塗りつぶしの指定
}

// チーム名部分 描画処理　
// ※メッセージ部分とほぼ同様の記述で、スタイルを一部調整した内容になるので割愛


```
* Canvasに用意されている各種メソッドで文字の描画位置やスタイルを設定
* 入力を受け付けたときに描画処理を行う

※Canvasを使った一連の描画処理は以下の記事・コードを参考にさせていただきました

https://web-breeze.net/add-text-to-image-and-share/

### 3. 作成物を画像データとしてダウンロードする

```index.js
// 作成ボタン押下時にcanvasを画像でダウンロード
document.getElementById('btn_dl').addEventListener('click', downloadCanvas);

// --（中略）--

// Canvasを合成
var createImage = function (context) {
    var image = new Image();
    image.src = context.canvas.toDataURL();
    return image;
}

function downloadCanvas() {
    // canvasのstyleにborderを追加
    canvas_m.style.border = "2px solid #222222";

    // URL取得用のa要素を生成
    let link = document.createElement("a");

    link.href = canvas_m.toDataURL("image/png");

    link.download = "image.png";
    link.click();
}

```
* 最後にCanvasの生成内容を合成して、`toDataURL()`メソッドで画像データURIへ変換
* a要素を生成し、擬似的にクリックイベントを発動してデータDLを実行させる

※以下の記事・コードを参考にさせていただきました

https://qiita.com/iwaimagic/items/1d16a721b36f04e91aed

* 作成ボタン（DLボタン）は入力値が空でないかをチェックし、メッセージ・チーム名いずれも入力がされている場合のみボタンが有効となるようにしました

### 4. Twitterシェア用のデフォルトテキストと遷移ボタン配置
* よくある「生成した画像をそのままデフォルトテキストとともに投稿」をやりたかったのですが、調べたところ一度生成した画像をサーバにアップロードする必要があるようで断念
* デフォルトテキストとTwitterトップへの遷移を配置しただけの超簡易的なシェア誘導のみ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974587/855b5171-43ef-8b8a-9d38-7f7aa65b5e1b.png)

# そのほか小ネタ
### カラー切り替え時に表示が崩れるバグへの対応
* 文字を入力した後に、ラジオボタンでカラーを切り替えた際、ベースの生地色や文字色が正しく出力されないバグが頻発していました。
    * 打率3割くらい（結構高いｗ）
    * ↓こんな感じ。ピンクを選択したのに、文字色やベースカラーが青になってしまっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974587/f4a84858-5a86-d68a-c023-1d83568e39f2.png)

* 対策がまったく分からず現役のエンジニアの先生に相談して助言をもらい、以下のように`setTimeout()`メソッドでベース部分の読み込みとテキスト描画をしなおすコードを追加したところ改善しました。
```index.js
// ラジオボタンが変更されたらidが持つ文字列をcolorに代入する
document.getElementById('bg_color'), addEventListener('change', colorChange);
function colorChange() {

    // 中略、ラジオボタンが選択されたときの処理をもろもろ記述

    // 入力時、稀に表示が崩れる不具合の対策としてTimeoutを設定
    setTimeout(function () {
        ctx_m.drawImage(bgImg, 0, 0);
        ctx_m.drawImage(bgImg, 0, 418, bgImg.width, bgImg_l.height, 0, 418, bgImg.width, bgImg_l.height);
        drawText(textBox.value);
        drawText_logo(logoBox.value);
    }, 200);
}
```
* どうやらカラーの変更と描画処理のタイミングが混ざってしまう場合があるのが原因のようで、指定ミリ秒後に表示をリセットさせる処理を入れることで防ぐことになっています

### 使用用途・番外編
* 99%ネタ使いして大喜利的に楽しむお遊びツールですが、作成した画像をプリントして実際のタオルテイストな応援ボードにして使うことができます（結局ネタ使いしかしてない←）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1974587/f4abab86-fd8c-977a-27ed-285a77d9090e.png)

# おわりに
* **Canvas、難しい。** 描画系は他にも便利なライブラリが幾つかあるようなので、今後また別の手法で何か制作に挑戦したいです。
* GitHub Pagesを使うことでとても手軽に自作サイトを公開できました。とにかく簡単でスピーディなので、今後も静的ページを制作する際には活用していこうと思いました。

* これを書いている今、バファローズはペナントレース残すところ2試合を控え、首位ホークスと優勝を懸けて薄氷の一騎打ち。何とか接戦を制し、ポストシーズンをもうひと盛り上がりさせて本ジェネレーターを活用させてほしいものです。や、活用できるのか？（）

お読みいただきありがとうございました！