CMS・WordPress

WordPressでACF Proなしで画像ギャラリー(繰り返しフィールド)を自作する方法【コピペOK】

WordPressで施工事例やポートフォリオを作成する際、複数の画像を登録・並び替えできる「繰り返しフィールド」が欲しくなることはありませんか?

Advanced Custom Fields(ACF)Proを使えば簡単に実装できますが、年間ライセンス費用がかかるのがネックです。

しかし、実は、画像ギャラリーの繰り返しフィールドはWordPressの標準機能だけで自作できます。

この記事では、ACF Proを使わずにカスタムメタボックスで画像ギャラリーを実装する方法を、実際のコード付きで解説します。

スポンサーリンク

この記事でできること

  • カスタム投稿タイプに画像ギャラリー機能を追加
  • WordPress標準のメディアライブラリから複数画像を選択
  • ドラッグ&ドロップで画像の並び替え
  • 個別の画像削除
  • ACF Proのライセンス費用を節約

完成イメージ

管理画面の投稿編集ページに、以下の機能を持つメタボックスが追加されます。

  • 「画像を追加」ボタンでメディアライブラリが開く
  • サムネイル一覧が表示され、ドラッグで並び替え可能
  • 各画像の右上に削除ボタン(×)を配置

WordPressでACF Proなしで画像ギャラリー(繰り返しフィールド)を自作する方法【コピペOK】

前提条件

  • カスタム投稿タイプ works(施工事例)を今記事の例とする
  • カスタム投稿タイプ works(施工事例)が登録済みであること
  • WordPress 5.0以上であること

カスタム投稿タイプの登録がまだの場合は、functions.php に register_post_type() で works を登録してください。

実装の全体像

実装は大きく4つのパートに分かれます。

  1. 管理画面用スクリプトの読み込み — メディアライブラリとSortableを有効化
  2. メタボックスの追加 — 投稿編集画面にギャラリーUIを表示
  3. メタボックスのHTML/JS — 画像選択・削除・並び替えのUI実装
  4. 保存処理 — 画像IDの配列をポストメタに保存
スポンサーリンク

コード解説

以下のコードを functions.php に追加してください。

1. 管理画面用スクリプトの読み込み

function works_gallery_admin_scripts($hook) {
 global $post_type;
 if (($hook === 'post.php' || $hook === 'post-new.php') && $post_type === 'works') {
  wp_enqueue_media();
  wp_enqueue_script('jquery-ui-sortable');
 }
}
add_action('admin_enqueue_scripts', 'works_gallery_admin_scripts');

wp_enqueue_media() でWordPress標準のメディアアップローダーを、jquery-ui-sortable でドラッグ&ドロップの並び替え機能を読み込みます。

$hook と $post_type で条件を絞ることで、施工事例の編集画面でのみスクリプトをロードし、他の管理画面への影響を防いでいます。

2. メタボックスの登録

function works_gallery_meta_box() {
 add_meta_box(
  'works_gallery', // メタボックスのID
  '施工事例 画像ギャラリー', // 表示タイトル
  'works_gallery_meta_box_callback', // コールバック関数
  'works', // 対象の投稿タイプ
  'normal', // 表示位置
  'high' // 優先度
 );
}
add_action('add_meta_boxes', 'works_gallery_meta_box');

add_meta_box() で投稿編集画面にカスタムメタボックスを追加します。

第5引数の 'normal' はエディタの下に表示する指定、'high' は他のメタボックスより上に表示する優先度の指定です。

3. メタボックスのHTML出力とJavaScript

ここがメインの実装部分です。

少し長いですが、やっていることはシンプルです。

// メタボックスのHTML出力
function works_gallery_meta_box_callback($post) {
  wp_nonce_field('works_gallery_nonce_action', 'works_gallery_nonce');
  $images = get_post_meta($post->ID, '_works_gallery_images', true);
  if (!is_array($images)) $images = array();
  ?>
  <style>
   #works-gallery-container {
     display: flex;
     flex-wrap: wrap;
     gap: 5px;
   }
   .works-gallery-item {
     cursor: grab;
     position: relative;
     display: inline-block;
   }
   .works-gallery-item:active {
     cursor: grabbing;
   }
   .works-gallery-item.ui-sortable-helper {
     opacity: 0.8;
     box-shadow: 0 4px 12px rgba(0,0,0,0.3);
     transform: scale(1.05);
   }
   .sortable-placeholder {
     width: 150px;
     height: 150px;
     border: 2px dashed #0073aa;
     background: #f0f6fc;
     display: inline-block;
     margin: 5px;
   }
 </style>
 <div id="works-gallery-container">
  <?php foreach ($images as $index => $image_id) :
   $image_url = wp_get_attachment_image_url($image_id, 'thumbnail');
   if (!$image_url) continue;
  ?>
  <div class="works-gallery-item">
    <img src="<?php echo esc_url($image_url); ?>" style="width:150px; height:150px; object-fit:cover; display:block;">
    <input type="hidden" name="works_gallery_images[]" value="<?php echo esc_attr($image_id); ?>">
    <button type="button" class="works-gallery-remove" style="position:absolute; top:0; right:0; background:red; color:#fff; border:none; cursor:pointer; padding:2px 6px;">×</button>
  </div>
  <?php endforeach; ?>
 </div>
 <p>
   <button type="button" id="works-gallery-add" class="button">画像を追加</button>
 </p>
 <p class="description">※ 画像をドラッグ&ドロップで並び替えできます</p>
 <script>
  jQuery(document).ready(function($) {
    $('#works-gallery-add').on('click', function(e) {
      e.preventDefault();
      var frame = wp.media({
        title: '画像を選択',
        multiple: true,
        library: { type: 'image' },
        button: { text: '画像を追加' }
      });
      frame.on('select', function() {
        var attachments = frame.state().get('selection').toJSON();
        $.each(attachments, function(i, attachment) {
          var thumbUrl = attachment.sizes.thumbnail ? attachment.sizes.thumbnail.url : attachment.url;
          var html = '<div class="works-gallery-item">';
          html += '<img src="' + thumbUrl + '" style="width:150px; height:150px; object-fit:cover; display:block;">';
          html += '<input type="hidden" name="works_gallery_images[]" value="' + attachment.id + '">';
          html += '<button type="button" class="works-gallery-remove" style="position:absolute; top:0; right:0; background:red; color:#fff; border:none; cursor:pointer; padding:2px 6px;">×</button>';
          html += '</div>';
          $('#works-gallery-container').append(html);
        });
      });
      frame.open();
    });
    $(document).on('click', '.works-gallery-remove', function() {
    $(this).closest('.works-gallery-item').remove();
  });
  $('#works-gallery-container').sortable({
    items: '.works-gallery-item',
    cursor: 'grabbing',
    placeholder: 'sortable-placeholder',
    tolerance: 'pointer',
    revert: 200
  });
});
</script>
<?php 
}

ポイント解説

nonceフィールド:wp_nonce_field() でCSRF対策を実施しています。保存時にこのnonceを検証することで、不正なリクエストを防ぎます。

hidden input:各画像の attachment ID を works_gallery_images[] という配列名のhiddenフィールドで保持します。Sortableで並び替えるとDOM順が変わるため、フォーム送信時に自動的に正しい順序で送信されます。

wp.media:WordPressに組み込まれているメディアライブラリのモーダルを利用しています。multiple: true で複数選択に対応しています。

jQuery UI Sortable:.sortable() を呼ぶだけでドラッグ&ドロップの並び替えが実現します。placeholder オプションで、ドロップ先に青い点線の枠を表示しています。

4. 保存処理

function works_gallery_save($post_id) {
 // nonceの検証
 if (!isset($_POST['works_gallery_nonce']) ||
 !wp_verify_nonce($_POST['works_gallery_nonce'], 'works_gallery_nonce_action')) return;

 // 自動保存時はスキップ
 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;

 // 権限チェック
 if (!current_user_can('edit_post', $post_id)) return;

 // 画像IDの保存
 if (isset($_POST['works_gallery_images'])) {
  $images = array_map('intval', $_POST['works_gallery_images']);
  update_post_meta($post_id, '_works_gallery_images', $images);
 } else {
  delete_post_meta($post_id, '_works_gallery_images');
 }
}
add_action('save_post_works', 'works_gallery_save');

保存処理では3つのセキュリティチェックを行っています。

  • nonce検証:リクエストが正規のフォームからであることを確認
  • 自動保存の除外:WordPressの自動保存でメタデータが消えるのを防止
  • 権限チェック:投稿を編集する権限を持つユーザーかを確認

array_map('intval', ...) で送信された値をすべて整数に変換し、不正な値の混入を防いでいます。

save_post_works フックは save_post_{post_type} 形式で、施工事例(works)の保存時にのみ発火します。

フロントエンドでの表示方法

保存した画像をフロントエンドで表示するには、テンプレートファイルに以下のコードを追加します。

<?php
 $images = get_post_meta(get_the_ID(), '_works_gallery_images', true);
 if (!empty($images) && is_array($images)) : ?>
 <div class="works-gallery">
 <?php foreach ($images as $image_id) :
 $image_url = wp_get_attachment_image_url($image_id, 'large');
 $image_alt = get_post_meta($image_id, '_wp_attachment_image_alt', true);
 if (!$image_url) continue;
 ?>
  <figure class="works-gallery__item">
   <img src="<?php echo esc_url($image_url); ?>"
    alt="<?php echo esc_attr($image_alt); ?>"
    loading="lazy">
  </figure>
<?php endforeach; ?>
</div>
<?php endif; ?>

wp_get_attachment_image_url() の第2引数でサイズを指定できます。

一覧ページでは medium、詳細ページでは large や full を使い分けるとよいでしょう。

まとめ

ACF Proの繰り返しフィールドは便利ですが、画像ギャラリー程度の機能であればWordPressの標準機能だけで十分に実装できます。

今回紹介したコードは約100行程度で、メディアライブラリの活用、ドラッグ&ドロップの並び替え、セキュリティ対策まで含んだ実用的な実装です。プラグインへの依存を減らしたい方や、WordPressの内部構造を学びたい方はぜひ試してみてください。

 

スポンサーリンク