Wordpress用のアイキャッチ自動生成プラグインを作ってみた

## はじめに
メディアを運用していくにあたり、アイキャッチ作成って意外とコスト取ってますよね
実際にメディアを運用していてアイキャッチ作成には時間を取っていた（私もphotoshopで作ってました）

じゃあ仕組み化したらいいんじゃね？ということで表題の通り仕組み化(プラグインに)しました。

まあこんなアイキャッチができます
![eyecatch-210.png](https://qiita-image-store.s3.amazonaws.com/0/214581/70bf6b77-3952-4e97-1efb-7c5684cfd565.png)

## 技術要件
- canvas
- php
- Jquery
- Wordpress

## ロジック
Wordpresssの投稿画面で動作する。挙動としてはテキストフィールドに入力された文字と、Wordpressに登録されサイトのロゴを自動でcanvasへ適用する。
また、canvasは描画コンテンツを画像にできるのでクライアントサイドだけでアイキャッチを作ることができる。

## 開発フロー
以下の順序で開発した
1. add_meta_boxでサイドバーにアイキャッチジェネレータなるものを追加
2. meta_box内にinputやbutton,canvas等HTMLタグを出力
3. canvasにユーザが入力した文字を描画

### 1.add_meta_boxでサイドバーにアイキャッチジェネレータなるものを追加
add_meta_boxのロジックは [ここ](https://blog.sus-happy.net/wp-customfield-custom/ "ここ")が参考になります

```php
function add_side_meta_box_free_generator() {
	if (has_post_thumbnail()) {
		add_meta_box( 'free_eyecatch_gene', '無料版アイキャッチジェネレーター', 'add_side_free_gene_meta_box_callback', 'post', 'side', 'default' );
	}
}
add_action( 'add_meta_boxes', 'add_side_meta_box_free_generator' );

``` 
add_side_free_gene_meta_box_callbackがmeta_boxに入れるコンテンツを描画するfunctionになります。次で書きます。

### 2. meta_box内にinputやbutton,canvas等HTMLタグを出力
小さすぎるとアイキャッチとして適さない画質になるので結構大きめの画像を生成するようにしてます。画像の比率は4:3です。

```php
function add_side_free_gene_meta_box_callback() { ?>
	<canvas width="1000" height="750" id="generator-view-free"></canvas>
    <input type="text" class="generator-title-free" placeholder="一つ目のタイトルを入れてください" maxlength="15" style="width:100%">
    <p style="color: red; font-size: 12px; margin-top: 0;">※一つ目のタイトルは（全角）15文字までです</p>
    <input type="text" class="generator-title-free2" placeholder="二つ目のタイトルを入れてください" maxlength="30" style="width:100%">
    <p style="color: red; font-size: 12px; margin-top: 0;">※二つ目のタイトルは（全角）30文字までです(15文字を越えると自動で改行してくれます)</p>
    <button type="button" class="generator-btn-free">作成</button>
    <img src="" alt="" class="generated-img">
<?php }
```

### 3. canvasにユーザが入力した文字を描画

```php

add_action( 'edit_form_advanced', 'disp_eyecatch_free_generator' );
function disp_eyecatch_free_generator($post){ ?>
    <?php
    $image_id = get_post_thumbnail_id();
    $image_url = wp_get_attachment_image_src($image_id, true);
    ?>
    <script type="text/javascript">
    var isStore = false;
    var canvasFree = document.querySelector("#generator-view-free");
    var contextFree = canvasFree.getContext("2d");
    var imgFree = new Image();
    var imgFree2 = new Image();
    function draw() {
        imgFree.src = "<?php echo $image_url[0]; ?>";
        imgFree.onload = function() {
            // clip case
            // height > width
            if (this.height > this.width) {
                contextFree.drawImage(this, 0, 0, 1000, (this.height / this.width) * 1000);
            } else {
                contextFree.drawImage(this, 0, 0, (this.width / this.height) * 750, 750);
            }
            contextFree.fillStyle = 'rgba(0, 0, 0, 0.4)';
            contextFree.fillRect(0, 0, 1000, 750);
            contextFree.fillStyle = 'rgba(255, 255, 255, 0.8)';
            contextFree.fillRect(0, 550, 1000, 200);
            imgFree2.src = "<?php echo( get_header_image() ); ?>";
            imgFree2.onload = function() {
                const aspect = this.height / this.width;
                const baseImgWidth = 1000 / 3;
                // basicaly, img width is one per three by canvasFree width;
                contextFree.drawImage(this, 500 - baseImgWidth/2, 650 - (baseImgWidth * aspect) / 2, baseImgWidth, baseImgWidth * aspect);
            }
        }
    };
    function strIns(str, idx, val){
        var res = str.slice(0, idx) + val + str.slice(idx);
        return res;
    };

    function fillTextLine(ctx, text, x, y) {
        let textList = text.split('\n');
        let lineHeight = ctx.measureText("あ").width;
        textList.forEach(function(text, i) {
            if (i === 0) {
                ctx.fillText(text, x, y+lineHeight*i);
            } else {
                ctx.fillText(text, (canvasFree.width / 2 - (text.length / 2 * 60)), y+lineHeight*i + 75);
            }
        });
    };
    $(function(){
      draw();
      $('.generator-btn-free').click(function() {
          let textFree = $('.generator-title-free').val();
          let textFree2 = $('.generator-title-free2').val();
          if (textFree2.length > 15) {
              textFree2 = strIns(textFree2, 15, '\n');
          }
          if (imgFree.height > imgFree.width) {
              contextFree.drawImage(imgFree, 0, 0, 1000, (imgFree.height / imgFree.width) * 1000);
          } else {
              contextFree.drawImage(imgFree, 0, 0, (imgFree.width / imgFree.height) * 750, 750);
          }
          contextFree.fillStyle = 'rgba(0, 0, 0, 0.4)';
          contextFree.fillRect(0, 0, 1000, 750);
          contextFree.fillStyle = 'rgba(255, 255, 255, 0.8)';
          contextFree.fillRect(0, 550, 1000, 200);
          const aspect = imgFree2.height / imgFree2.width;
          const baseImgWidth = 1000 / 3;
          // basicaly, img width is one per three by canvasFree width;
          contextFree.drawImage(imgFree2, 500 - baseImgWidth/2, 650 - (baseImgWidth * aspect) / 2, baseImgWidth, baseImgWidth * aspect);
          contextFree.font = "normal 60px 'Arial'";
          contextFree.fillStyle = "white";
          contextFree.fillText(
            textFree,
            (canvasFree.width / 2 - (textFree.length / 2 * 60)),
            165
          );
          const x = textFree2.length > 15 ? (canvasFree.width / 2 - 450) : (canvasFree.width / 2 - (textFree2.length / 2 * 12));
          fillTextLine(contextFree, textFree2, x, 300);
      });
    });
    </script>
    <style>
    canvas#generator-view-free {
        width: 100%;
    }
    </style>
<?php }
```

上から順番に説明していきます
まずはこれ

```php
    $image_id = get_post_thumbnail_id();
    $image_url = wp_get_attachment_image_src($image_id, true);
```

これは投稿に設定されている現状のアイキャッチ画像を取得して画像のURLを後々生成するアイキャッチの背景に使います。

```javascript

    function draw() {
        imgFree.src = "<?php echo $image_url[0]; ?>";
        imgFree.onload = function() {
            // clip case
            // height > width
            if (this.height > this.width) {
                contextFree.drawImage(this, 0, 0, 1000, (this.height / this.width) * 1000);
            } else {
                contextFree.drawImage(this, 0, 0, (this.width / this.height) * 750, 750);
            }
            contextFree.fillStyle = 'rgba(0, 0, 0, 0.4)';
            contextFree.fillRect(0, 0, 1000, 750);
            contextFree.fillStyle = 'rgba(255, 255, 255, 0.8)';
            contextFree.fillRect(0, 550, 1000, 200);
            imgFree2.src = "<?php echo( get_header_image() ); ?>";
            imgFree2.onload = function() {
                const aspect = this.height / this.width;
                const baseImgWidth = 1000 / 3;
                // basicaly, img width is one per three by canvasFree width;
                contextFree.drawImage(this, 500 - baseImgWidth/2, 650 - (baseImgWidth * aspect) / 2, baseImgWidth, baseImgWidth * aspect);
            }
        }
    };
```

先ほど取得した画像を背景として設定するためにimgFree.srcに入れonloadでcanvasに描画してます。
途中にclip caseとコメントアウトされてるとこの下になにやら計算しているのがありますがこれは画像のサイズを自動で調整して背景画像をフルサイズ表示するための計算です。

次に、画像を描画後rgba(0,0,0,0.4)で透過済みのレクタングルを描画
そして、wordpressに設定されてるサイトロゴ（get_header_image()）をimgFree2.srcに入れ同様にonloadで描画してます。

```javascript
    function strIns(str, idx, val){
        var res = str.slice(0, idx) + val + str.slice(idx);
        return res;
    };

    function fillTextLine(ctx, text, x, y) {
        let textList = text.split('\n');
        let lineHeight = ctx.measureText("あ").width;
        textList.forEach(function(text, i) {
            if (i === 0) {
                ctx.fillText(text, x, y+lineHeight*i);
            } else {
                ctx.fillText(text, (canvasFree.width / 2 - (text.length / 2 * 60)), y+lineHeight*i + 75);
            }
        });
    };
    $(function(){
      draw();
      $('.generator-btn-free').click(function() {
          let textFree = $('.generator-title-free').val();
          let textFree2 = $('.generator-title-free2').val();
          if (textFree2.length > 15) {
              textFree2 = strIns(textFree2, 15, '\n');
          }
          if (imgFree.height > imgFree.width) {
              contextFree.drawImage(imgFree, 0, 0, 1000, (imgFree.height / imgFree.width) * 1000);
          } else {
              contextFree.drawImage(imgFree, 0, 0, (imgFree.width / imgFree.height) * 750, 750);
          }
          contextFree.fillStyle = 'rgba(0, 0, 0, 0.4)';
          contextFree.fillRect(0, 0, 1000, 750);
          contextFree.fillStyle = 'rgba(255, 255, 255, 0.8)';
          contextFree.fillRect(0, 550, 1000, 200);
          const aspect = imgFree2.height / imgFree2.width;
          const baseImgWidth = 1000 / 3;
          // basicaly, img width is one per three by canvasFree width;
          contextFree.drawImage(imgFree2, 500 - baseImgWidth/2, 650 - (baseImgWidth * aspect) / 2, baseImgWidth, baseImgWidth * aspect);
          contextFree.font = "normal 60px 'Arial'";
          contextFree.fillStyle = "white";
          contextFree.fillText(
            textFree,
            (canvasFree.width / 2 - (textFree.length / 2 * 60)),
            165
          );
          const x = textFree2.length > 15 ? (canvasFree.width / 2 - 450) : (canvasFree.width / 2 - (textFree2.length / 2 * 12));
          fillTextLine(contextFree, textFree2, x, 300);
      });
```

- strIns functionは指定した文字数を越えると\nを挿入してくれるメソッド
- fillTextLine functionは先ほどのstrInsで挿入した\nを元にsplitし配列するメソッド

あとは以下のclickイベントを検出後テキストフィールドの文字や画像をcanvasへ描画します。

```
 $('.generator-btn-free').click
```

## 最後に
以上のサンプルコードを元にアイキャッチを生成する処理を体験してみてください。
### 最終コード

以下をfunction.phpに貼り付けるだけでも動作します。

```php
function add_side_meta_box_free_generator() {
	if (has_post_thumbnail()) {
		add_meta_box( 'free_eyecatch_gene', '無料版アイキャッチジェネレーター', 'add_side_free_gene_meta_box_callback', 'post', 'side', 'default' );
	}
}

/**
 * 枠の内容を出力します。
 */
function add_side_free_gene_meta_box_callback() { ?>
	<canvas width="1000" height="750" id="generator-view-free"></canvas>
    <input type="text" class="generator-title-free" placeholder="一つ目のタイトルを入れてください" maxlength="15" style="width:100%">
    <p style="color: red; font-size: 12px; margin-top: 0;">※一つ目のタイトルは（全角）15文字までです</p>
    <input type="text" class="generator-title-free2" placeholder="二つ目のタイトルを入れてください" maxlength="30" style="width:100%">
    <p style="color: red; font-size: 12px; margin-top: 0;">※二つ目のタイトルは（全角）30文字までです(15文字を越えると自動で改行してくれます)</p>
    <button type="button" class="generator-btn-free">作成</button>
    <img src="" alt="" class="generated-img">
<?php }
add_action( 'add_meta_boxes', 'add_side_meta_box_free_generator' );

add_action( 'edit_form_advanced', 'disp_eyecatch_free_generator' );
function disp_eyecatch_free_generator($post){ ?>
    <?php
    $image_id = get_post_thumbnail_id();
    $image_url = wp_get_attachment_image_src($image_id, true);
    ?>
    <script type="text/javascript">
    var isStore = false;
    var canvasFree = document.querySelector("#generator-view-free");
    console.log(canvasFree);
    var contextFree = canvasFree.getContext("2d");
    var imgFree = new Image();
    var imgFree2 = new Image();
    function draw() {
        console.log(canvasFree);
        imgFree.src = "<?php echo $image_url[0]; ?>";
        console.log(imgFree);
        imgFree.onload = function() {
            console.log(this.width, this.height);
            // clip case
            // height > width
            if (this.height > this.width) {
                contextFree.drawImage(this, 0, 0, 1000, (this.height / this.width) * 1000);
            } else {
                contextFree.drawImage(this, 0, 0, (this.width / this.height) * 750, 750);
            }
            contextFree.fillStyle = 'rgba(0, 0, 0, 0.4)';
            contextFree.fillRect(0, 0, 1000, 750);
            contextFree.fillStyle = 'rgba(255, 255, 255, 0.8)';
            contextFree.fillRect(0, 550, 1000, 200);
            imgFree2.src = "<?php echo( get_header_image() ); ?>";
            console.log("<?php echo( get_header_image() ); ?>");
            imgFree2.onload = function() {
                const aspect = this.height / this.width;
                const baseImgWidth = 1000 / 3;
                console.log(baseImgWidth * aspect);
                // basicaly, img width is one per three by canvasFree width;
                contextFree.drawImage(this, 500 - baseImgWidth/2, 650 - (baseImgWidth * aspect) / 2, baseImgWidth, baseImgWidth * aspect);
            }
        }
    };
    function strIns(str, idx, val){
        var res = str.slice(0, idx) + val + str.slice(idx);
        return res;
    };

    function fillTextLine(ctx, text, x, y) {
        let textList = text.split('\n');
        let lineHeight = ctx.measureText("あ").width;
        textList.forEach(function(text, i) {
            if (i === 0) {
                ctx.fillText(text, x, y+lineHeight*i);
            } else {
                ctx.fillText(text, (canvasFree.width / 2 - (text.length / 2 * 60)), y+lineHeight*i + 75);
            }
        });
    };
    $(function(){
      draw();
      $('.generator-btn-free').click(function() {
          let textFree = $('.generator-title-free').val();
          let textFree2 = $('.generator-title-free2').val();
          if (textFree2.length > 15) {
              console.log('into state');
              textFree2 = strIns(textFree2, 15, '\n');
          }
          console.log('click eve', textFree, textFree2);
          if (imgFree.height > imgFree.width) {
              contextFree.drawImage(imgFree, 0, 0, 1000, (imgFree.height / imgFree.width) * 1000);
          } else {
              contextFree.drawImage(imgFree, 0, 0, (imgFree.width / imgFree.height) * 750, 750);
          }
          contextFree.fillStyle = 'rgba(0, 0, 0, 0.4)';
          contextFree.fillRect(0, 0, 1000, 750);
          contextFree.fillStyle = 'rgba(255, 255, 255, 0.8)';
          contextFree.fillRect(0, 550, 1000, 200);
          const aspect = imgFree2.height / imgFree2.width;
          const baseImgWidth = 1000 / 3;
          // basicaly, img width is one per three by canvasFree width;
          contextFree.drawImage(imgFree2, 500 - baseImgWidth/2, 650 - (baseImgWidth * aspect) / 2, baseImgWidth, baseImgWidth * aspect);
          contextFree.font = "normal 60px 'Arial'";
          contextFree.fillStyle = "white";
          contextFree.fillText(
            textFree,
            (canvasFree.width / 2 - (textFree.length / 2 * 60)),
            165
          );
          const x = textFree2.length > 15 ? (canvasFree.width / 2 - 450) : (canvasFree.width / 2 - (textFree2.length / 2 * 12));
          fillTextLine(contextFree, textFree2, x, 300);
      });
    });
    </script>
    <style>
    canvas#generator-view-free {
        width: 100%;
    }
    </style>
<?php }
```

## とりあえず使いたい方へ
[こちら](https://github.com/miyabiya/eyecatch-generator-free)にソースあげてます
使い方
1.clone or downladをクリック
2.download zipをクリック
3.ダウンロードしたzipをwordpress追加:[pluginの追加方法はこちら](https://www.adminweb.jp/wordpress/plugin/index2.html)

## 補足
他社のマーケターにこちらの機能を使ってもらったところ好評でした。
自分がやってたようにアイキャッチを作成するにはバナーを作るサービスやphotoshopを元に作成しますがやはり、視認性の良いアイキャッチを作成するには属人的なところが大きいのでパターンを考える必要がありました。そのためあえてツール側でパターンを用意することで作成コストが短縮できるのとライター自身にアイキャッチの作成を任せられるところが大き方のかと思います。

また、要望を元にさらに機能を拡張したpluginを作成しました。後々掲載いたします。
機能拡張により以下の機能を追加してます

- ダウンロードボタン
- 画像素材検索
- 画像をアップロードできる
- カラーピッカーで背景色変更
- リアルタイムでテキスト変更
- アイキャッチのパターンが10種以上
- 文字サイズ変更可