縦長画像のアイキャッチの左右余白を埋めて表示する方法

Cocoonカスタマイズ集
この記事は約18分で読めます。
記事内に広告が含まれています。

記事に記載したコードは、スキンに追加されています。

画像として合成して表示する方法

以下ではライブラリ「html2canvas」を使用して作成しました。

しかし、html2canvasではobject-fitやfilterが未サポートのため、代わりにライブラリ「dom-to-image」を使用します。

完成イメージ

実装手順

プラグインの形式で実装します。

以下の手順で実装します。

  • my-html-image-blur
    • html-image-blur
    • assets
      • css
        • admin.css
      • js
        • admin.js
  • ラベル
    PHPを追加

    以下のコードをhtml-image-blur.phpに追加します。

    <?php
    /**
     * Plugin Name: My HTML Image Blue
     * Description: 縦長画像に背景のぼかしを画像として合成します。
     * Version: 1.0.0
     * Author: CHU-YA
     */
    
    if (!defined('ABSPATH')) {
      exit;
    }
    
    class AFI_Featured_Image_Blur_Generator {
      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(
          'vertical_featured_blur',
          'アイキャッチ画像自動生成',
          [$this, 'vertical_featured_blur'],
          'post',
          'side'
        );
      }
    
    
      /**
       * メタボックスの入力フォームを出力
       *
       * @param WP_Post $post 現在編集中の投稿オブジェクト
       * @return void
       */
      public function vertical_featured_blur($post) {
        ?>
        <div id="vertical-featured">
          <p>
            <button type="button" class="button" id="afi_select_image">画像を選択</button>
            <input type="hidden" id="afi_selected_image_url" value="">
          </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
      }
    
    
      /**
       * エディター用JS・CSを追加
       *
       * @param string $hook 現在の管理画面フック名
       * @return void
       */
      public function enqueue_assets($hook) {
        // 投稿編集画面以外では読み込まない
        if (!in_array($hook, ['post.php', 'post-new.php'])) {
          return;
        }
    
        global $post;
    
        // メディアアップローダー読み込み
        wp_enqueue_media();
    
        // 外部ライブラリdom-to-image-moreの読み込み
        wp_enqueue_script('dom-to-image-more', 'https://cdnjs.cloudflare.com/ajax/libs/dom-to-image-more/3.5.0/dom-to-image-more.min.js', [], null, true);
    
        wp_enqueue_script('afi_js', plugin_dir_url(__FILE__) . 'assets/js/admin.js', ['jquery', 'dom-to-image-more'], null, true);
        wp_enqueue_style('afi_css', plugin_dir_url(__FILE__) . 'assets/css/admin.css');
    
        // Ajaxで使うパラメータなどをJS側に渡す
        wp_localize_script('afi_js', 'afiData', [
          'ajaxUrl'   => admin_url('admin-ajax.php'),
          'postId'    => $post->ID,
          'nonce'     => wp_create_nonce('vertical_featured_blur_action'),
          'cardRatio' => get_thumbnail_aspect_ratio(),
        ]);
      }
    
    
      /**
       * Ajaxアクションを登録
       *
       * @return void
       */
      public function ajax_save_image() {
        // nonceチェック
        check_ajax_referer(
          'vertical_featured_blur_action',
          'vertical_featured_blur_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);
    
        // 更新情報をJSON形式で返却
        wp_send_json_success([
          'url'       => $upload['url'],
          'attach_id' => $attach_id,
        ]);
      }
    }
    
    
    new AFI_Featured_Image_Blur_Generator();
  • ラベル
    JavaScriptを追加

    以下のコードをadmin.jsに追加します。

    jQuery(function ($) {
      const width = 800;
      const height = width * afiData.cardRatio;
    
      let currentImageData = '';
      let selectedImageUrl = '';
    
      /**
       * 画像生成用のHTMLテンプレートを作成
       *
       * @param {string} imageUrl 画像URL
       * @returns {string} HTML文字列
       */
      function generateTemplate(imageUrl) {
        const html = `
    <div id="afi_preview_container" style="width: ${width}px; height: ${height}px;">
      <div class="vertical-featured">
        <img class="vertical-featured__blur" src="${imageUrl}" />
        <img class="vertical-featured__main" src="${imageUrl}" />
      </div>
    </div>
        `;
    
        console.log(html);
    
        return html;
      }
    
    
      /**
       * HTMLをPNGに変換してプレビュー表示
       *
       * @param {string} htmlContent HTML文字列
       * @returns {void}
       */
      function htmlToPng(htmlContent) {
        const $container = $('<div>')
          .html(htmlContent)
          .appendTo('body');
    
        const element = $container.find('#afi_preview_container')[0];
    
        if (!element) {
          console.error('プレビュー要素が見つかりません');
          $container.remove();
          return;
        }
    
        domtoimage.toPng(element, {
          cacheBust: true
        })
          .then(function (dataUrl) {
            currentImageData = dataUrl;
    
            $('#afi_preview')
              .attr('src', dataUrl)
              .show();
          })
          .catch(function (error) {
            console.error('dom-to-image Error:', error);
            alert('画像の生成に失敗しました。');
          })
          .finally(function () {
            $container.remove();
          });
      }
    
    
      // 画像選択ボタンのクリックイベント
      $('#afi_select_image').on('click', function () {
        const frame = wp.media({
          title: '画像を選択',
          button: {
            text: 'この画像を使用'
          },
          multiple: false
        });
    
        frame.on('select', function () {
          const attachment = frame
            .state()
            .get('selection')
            .first()
            .toJSON();
    
          selectedImageUrl = attachment.url;
    
          $('#afi_selected_image_url').val(selectedImageUrl);
    
          $('#afi_preview')
            .attr('src', selectedImageUrl)
            .show();
        });
    
        frame.open();
      });
    
    
      // 画像作成ボタンのクリックイベント
      $('#afi_generate').on('click', function () {
        if (!selectedImageUrl) {
          alert('画像を選択してください');
          return;
        }
    
        const htmlContent = generateTemplate(selectedImageUrl);
    
        htmlToPng(htmlContent);
      });
    
    
      // 画像保存ボタンのクリックイベント
      $('#afi_save').on('click', function () {
        if (!currentImageData) {
          alert('画像を作成してください。');
          return;
        }
    
        $.post(afiData.ajaxUrl, {
          action: 'afi_save_image',
          vertical_featured_blur_nonce: afiData.nonce,
          postId: afiData.postId,
          imageData: currentImageData
        }, function (res) {
          if (res.success) {
            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を追加

    以下のコードをadmin.cssに追加します。

    .vertical-featured {
      height: 100%;
      overflow: hidden;
      position: relative;
      width: 100%;
    }
    
    .vertical-featured__blur {
      filter: blur(5px);
      height: 100%;
      left: 0;
      object-fit: cover;
      position: absolute;
      scale: 1.05;
      top: 0;
      width: 100%;
    }
    
    .vertical-featured__main {
      display: block;
      height: 100%;
      margin: 0 auto;
      position: relative;
      z-index: 1;
    }

さいごに

今回紹介した方法を使えば、縦長のアイキャッチ画像によって発生する左右の余白を、自然な背景ぼかしで美しく補うことができます。
CSSでの視覚的表現に加え、ぼかし背景を含めた画像として合成し、実際のアイキャッチとして設定することも可能です。

タイトルとURLをコピーしました