はじめに
Cocoon 2.8.4から投稿・固定ページの投稿タイトルからアイキャッチ画像を作成する機能が追加されました。
それに伴いJIN:Rなどの他テーマでも同様の機能がサポートされるようになっています。
Cocoonの機能の課題
PHPで画像処理を行う際には、主に以下の2つのライブラリが使用されます。
- GD(標準で利用可能)
- ImageMagick(拡張モジュールで、サーバー設定によっては使用不可)
Cocoonは、PHPに標準で備わっているGDライブラリがそのまま利用できるため、特別な設定なしで扱えるという利点から採用されています。
しかしGDではテキストのサイズや位置の調整、レイアウトの確認が非常に手間であり、以下のような課題があります。
- 要素の座標指定が必要で手間がかかる
- レイアウト確認には画像を都度出力する必要がある
画像生成方法の比較
表1に画像生成方法ごとの特徴をまとめます。
観点 | GD(PHP) | SVG + canvas(JS) | HTML+CSS + html2canvas(JS) |
---|---|---|---|
依存ライブラリ | 標準PHP拡張 | canvas API(JS) | html2canvas(外部JS) |
レイアウト確認のしやすさ | 都度出力が必要 | ブラウザ上で即時確認可 | DOMベースで即表示 |
レイアウト調整のしやすさ | 座標指定で煩雑 | 属性である程度調整可能 | CSSで直感的に調整可能 |
テキスト・フォント制御 | フォント制限あり | 柔軟な表現が可能 | Webフォント・CSSスタイル対応 |
要素の追加変更 | コードが煩雑になりやすい | SVG構造を保てば比較的容易 | HTML要素を追加するだけで済む |
再利用性・保守性 | カスタム関数が複雑化しやすい | テンプレート再利用可能 | HTMLテンプレート + CSS活用容易 |
目的
表1に示した利点を踏まえ、ライブラリ「html2canvas」を用いて、投稿タイトルからアイキャッチ画像を自動生成するプラグインを追加します。
使用テーマがCocoonである場合は、表1で指定した比率に従い画像を生成します。それ以外のテーマでは、縦横比3:4で画像が作成されます。
この記事では、この方法について説明します。
設定 | 大項目 | 項目 | 設定値 |
---|---|---|---|
画像 | サムネイル画像 |
完成イメージ

機能一覧
表2にプラグインの機能一覧を示します。
項目 | 説明 |
---|---|
背景色 | 背景の色を設定します。 |
文字色 | 文字の色を設定します。 |
枠色 | 枠の色を設定します。 |
著者名 | オフのとき、サイトアイコンとサイトのタイトルが表示します。 オンのとき、プロフィールアイジョンと投稿の著者名を表示します。 |
画像を作成 | クリックされると、画像生成しプレビュー表示します。 |
画像を保存 | クリックすると、メディアに保存され、アイキャッチ画像に設定します。 |
実装手順
以下の手順で実装します。
- html-canvas
- featured-image-form.php
- generator.js
- style.css
- ラベルPHPを追加
以下のコードをfeatured-image-form.phpに追加します。
<?php /* Plugin Name: アイキャッチ画像自動生成 Description: 投稿タイトルからアイキャッチ画像を生成します。 Version: 1.0 Author: CHU-YA */ if (!defined('ABSPATH')) exit; class AFI_Auto_Featured_Image_Generator { /** * コンストラクタ * メタボックス追加、スクリプト・スタイルの読み込み、Ajax処理のフックを設定する */ public function __construct() { add_action('add_meta_boxes', [$this, 'add_meta_box']); add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']); add_action('wp_ajax_afi_save_image', [$this, 'ajax_save_image']); } /** * 投稿編集画面にメタボックスを追加するコールバック * * @return void */ public function add_meta_box() { add_meta_box( 'afi_meta_box', 'アイキャッチ画像自動生成', [$this, 'render_meta_box'], 'post', 'side' ); } /** * メタボックスのHTMLを出力するコールバック * * @param WP_Post $post 現在編集中の投稿オブジェクト * @return void */ public function render_meta_box($post) { ?> <div id="afi_meta_box"> <p><label>背景色<input type="color" id="afi_bgcolor" value="#ffffff"></label></p> <p><label>文字色<input type="color" id="afi_fontcolor" value="#333333"></label></p> <p><label>枠色<input type="color" id="afi_framecolor" value="#a2d7dd"></label></p> <p><label><input type="checkbox" id="afi_use_author">著者名</label></p> <p><button type="button" class="button" id="afi_generate">画像を作成</button></p> <p><img id="afi_preview" src="" style="max-width:100%; display:none;"></p> <p><button type="button" class="button button-primary" id="afi_save">画像を保存</button></p> </div> <?php } /** * 投稿編集画面で必要なスクリプト・スタイルを読み込む * * @param string $hook 現在の管理画面フック名 * @return void */ public function enqueue_assets($hook) { // 投稿編集画面以外では読み込まない if (!in_array($hook, ['post.php', 'post-new.php'])) return; global $post; // 外部ライブラリhtml2canvasの読み込み wp_enqueue_script( 'html2canvas', 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', [], null, true ); // プラグイン独自のJS wp_enqueue_script( 'afi_js', plugin_dir_url(__FILE__) . 'generator.js', ['jquery'], null, true ); // プラグイン独自のCSS wp_enqueue_style( 'afi_css', plugin_dir_url(__FILE__) . 'style.css' ); // Ajaxで使うパラメータなどをJS側に渡す $author_id = get_post_field('post_author', $post->ID); $user = get_userdata($author_id); $author_name = get_the_author_meta('display_name', $author_id); $avatar_url = get_the_author_upladed_avatar_url($author_id); if (function_exists('get_thumbnail_aspect_ratio')) { $ratio = get_thumbnail_aspect_ratio(); } else { $ratio = 3 / 4; } // スクリプト向けの変数を追加 wp_localize_script('afi_js', 'afiData', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'postId' => $post->ID, 'nonce' => wp_create_nonce('afi_nonce'), 'cardRatio' => $ratio, 'siteIconUrl' => get_site_icon_url(64), 'siteName' => get_bloginfo('name'), 'authorName' => esc_js($author_name), 'authorAvatar' => esc_url($avatar_url), ]); } /** * Ajaxリクエストを受けて生成画像を保存し、投稿のアイキャッチ画像に設定する。 * * @return void JSON形式で成功/失敗を返す。 */ public function ajax_save_image() { // nonceチェック check_ajax_referer('afi_nonce', 'nonce'); $post_id = intval($_POST['postId']); $data = $_POST['imageData'] ?? ''; // 画像データ形式チェック(data URL) if (!preg_match('/^data:image\/png;base64,/', $data)) { wp_send_json_error(['message' => '無効な画像データ']); } // base64デコード $binary = base64_decode(str_replace('data:image/png;base64,', '', $data)); // ファイルをアップロードディレクトリに保存 $upload = wp_upload_bits("afi-{$post_id}.png", null, $binary); if (!empty($upload['error'])) { wp_send_json_error(['message' => $upload['error']]); } // 添付ファイル情報を作成 $attachment = [ 'post_mime_type' => 'image/png', 'post_title' => "AFI Image {$post_id}", 'post_status' => 'inherit', ]; // 添付ファイルとして登録 $attach_id = wp_insert_attachment($attachment, $upload['file'], $post_id); if (is_wp_error($attach_id)) { wp_send_json_error([ 'message' => '画像の保存に失敗しました', 'error_details' => $attach_id->get_error_message() ]); } // 画像メタデータ生成と更新 require_once ABSPATH . 'wp-admin/includes/image.php'; $meta = wp_generate_attachment_metadata($attach_id, $upload['file']); wp_update_attachment_metadata($attach_id, $meta); // 投稿のアイキャッチ画像に設定 set_post_thumbnail($post_id, $attach_id); // 成功レスポンス送信 wp_send_json_success([ 'url' => $upload['url'], 'attach_id' => $attach_id ]); } } new AFI_Auto_Featured_Image_Generator();
- ラベルJavaScriptを追加
以下のコードをgenerator.jsに追加します。
jQuery(function($) { const width = 800; const height = width * afiData.cardRatio; let currentImageData = ''; // タイトルを取得 function getCurrentTitle() { if (typeof wp !== 'undefined' && wp.data) { return wp.data.select('core/editor').getEditedPostAttribute('title') || ''; } return $('#title').val() || ''; } // プレビュー用のHTMLテンプレート生成 function generateHTML(fontColor, frameColor, titleText) { const displayMode = $('#afi_use_author').is(':checked'); let siteIconUrl = afiData.siteIconUrl; let siteName = afiData.siteName; // 著者名の場合 if (displayMode) { siteIconUrl = afiData.authorAvatar; siteName = afiData.authorName; } return ` <div id="afi_preview_container" style="border-color:${frameColor}; height: ${height}px; width:${width}px;"> <div id="afi_title" style="color:${fontColor};">${titleText}</div> <div id="afi_site"> <img src="${siteIconUrl}" alt="サイトアイコン" id="afi_icon"> <span id="afi_site_name" style="color:${fontColor};">${siteName}</span> </div> </div> `; } // HTMLをPNG画像に変換しimgに表示 function htmlToPng(htmlContent, bgColor) { const $container = $('<div>').html(htmlContent).appendTo('body'); const element = $container.find('#afi_preview_container')[0]; html2canvas(element, { useCORS: true, backgroundColor: bgColor }).then(function(canvas) { const img = canvas.toDataURL("image/png"); currentImageData = img; $('#afi_preview').attr('src', img).show(); }).catch(function(error) { console.error('html2canvas Error: ', error); alert('画像の生成に失敗しました。'); }).finally(function() { $container.remove(); }); } // 画像を作成ボタン $('#afi_generate').on('click', function () { const title = getCurrentTitle(); const bgColor = $('#afi_bgcolor').val(); const fontColor = $('#afi_fontcolor').val(); const frameColor= $('#afi_framecolor').val(); const htmlContent = generateHTML(fontColor, frameColor, title); htmlToPng(htmlContent, bgColor); }); // 画像を保存ボタン $('#afi_save').on('click', function () { if (!currentImageData) { alert('画像を作成してください。'); return; } $.post(afiData.ajaxUrl, { action: 'afi_save_image', nonce: afiData.nonce, postId: afiData.postId, imageData: currentImageData }, function(res) { if (res.success) { // Gutenbergエディターの場合 if (typeof wp !== 'undefined' && wp.data && wp.data.dispatch) { wp.data.dispatch('core/editor').editPost({ featured_media: res.data.attach_id }); } else { $('#_thumbnail_id').val(res.data.attach_id); } } else { alert('保存に失敗しました: ' + (res.data?.message || 'エラー')); } }); }); });
- ラベルCSSを追加
以下のコードをstyle.cssに追加します。
/* アイキャッチ画像自動生成メタボックス */ #afi_generate, #afi_save { display: block; width: 100%; } #afi_preview { display: none; width: 100%; } input[type="color"] { width: 100%; } /* アイキャッチ */ #afi_preview_container { border-style: solid; border-width: 30px; box-sizing: border-box; display: flex; flex-direction: column; padding: 40px; } /* タイトル */ #afi_title { align-content: center; flex: 1; font-size: 42px; font-weight: bold; line-height: 1.2; margin: 0; text-align: center; } /* サイト情報 */ #afi_site { align-items: center; display: flex; gap: 10px; } #afi_icon { height: 50px; width: 50px; } #afi_site_name { font-size: 30px; }
補足
CSSでbackground-color: transparent;
を指定しても、html2canvasでは背景が白く描画される場合があります。
そのため、背景を透明にしたい場合は、html2canvasのオプションbackgroundColor
にnull
またはrgba(0, 0, 0, 0)
のようにアルファ値が0の色を明示的に指定します。
参考
さいごに
今回紹介した方法を使えば、投稿タイトルからアイキャッチ画像を生成・保存できます。
Cocoon標準のGD方式と違い、HTMLとCSSで自由にデザインでき、即時プレビューでき、より柔軟なレイアウトを求める方におすすめです。