Wordpressで使えるアイキャッチ自動生成プラグインをリリースしました

業務中に派生したものを、一部個人で自由にライブラリ公開したりできる自由な環境です。nodejs,react native, vue,rails,goなど言語に興味あるのはもちろん、仮想通化に興味ある人一度ぜひ応募してください!

はじめに

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

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

まあこんなアイキャッチができます
もちろんこの記事のアイキャッチも自動で作ってます


技術要件

  • 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のロジックは ここが参考になります

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です。

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にユーザが入力した文字を描画

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 }

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

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

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

    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で描画してます。

   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に貼り付けるだけでも動作します。

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 }

とりあえず使いたい方へ

こちらにソースあげてます
使い方
1.clone or downladをクリック
2.download zipをクリック
3.ダウンロードしたzipをwordpress追加:pluginの追加方法はこちら

補足

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

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

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


業務中に派生したものを、一部個人で自由にライブラリ公開したりできる自由な環境です。nodejs,react native, vue,rails,goなど言語に興味あるのはもちろん、仮想通化に興味ある人一度ぜひ応募してください!

株式会社onetap's job postings
Anonymous
156baf8a 89e7 4df8 aa5a a70b9da73e48?1552545182
F8292373 387b 4c6d 84c7 3980dba8220c?1534123179
0d590750 ca0d 452d 9f43 08ce989902be?1529325369
Picture?1523068725
B0072d5a f0b8 4829 a185 fa20514e3c0c?1562204560
5 Likes
Anonymous
156baf8a 89e7 4df8 aa5a a70b9da73e48?1552545182
F8292373 387b 4c6d 84c7 3980dba8220c?1534123179
0d590750 ca0d 452d 9f43 08ce989902be?1529325369
Picture?1523068725
B0072d5a f0b8 4829 a185 fa20514e3c0c?1562204560
5 Likes

Weekly ranking

Show other rankings

Page top icon