Amazonで商品を見る セール会場へ

SWELL│記事一覧ページを「もっと見る」ボタン付き無限スクロールにする方法

WordPressテーマ「SWELL」で、記事一覧に「もっと見る」ボタンを設置して、非同期で読み込む無限スクロール(Load Moreボタン型)を実装した手順をまとめます。

テンプレートなど上書きしません、JSを追加してカスタマイズしています。

おまけとして、自動無限スクロールに変更するコードも追記しておきました。

目次

著者

WEB制作をしているデジタルノマド
WordPressのカスタマイズが好きで、色々と自作しています。

WordPressのカスタマイズに困ったらご相談ください!

SWELLで記事一覧ページを「もっと見る」ボタン付き無限スクロールにする方法

  • 通常の投稿のカテゴリー・タグアーカイブが対象(カスタム分類は対象外)
  • 3件ずつ追加で読み込み
  • ページネーションを残して失敗時も移動可能
  • コードはすべて functions.php に記述済み

表示設定の変更

記事一覧で表示する数を変更します。

検証では「設定 → 表示設定 → 1ページに表示する最大投稿数」を3件にしましたが、本番では希望する件数を指定できます。

管理画面の表示設定で、1ページに表示する最大投稿数を3に変更する操作画面。

functions.phpにコードを追加

有効な子テーマのfunctions.phpをバックアップして追加するか、Code Snippetsへ登録します。どちらか一方だけに追加し、親テーマは編集しません。

エラーが怖い人はCode Snippetsのプラグインを入れて、そこからコードを挿入しましょう。

コードをいれるだけです。

ページ番号は残します。JavaScriptが無効な場合や通信失敗時にも、通常のリンクで次ページへ移動できます。

add_action('wp_footer', 'my_add_loadmore_button');
function my_add_loadmore_button() {
	if ( ! is_category() && ! is_tag() ) return;

	global $wp_query;
	$term    = get_queried_object();
	$max     = $wp_query->max_num_pages;
	$paged   = max( 1, get_query_var( 'paged' ) );
	$term_id = $term->term_id ?? 0;
	$taxonomy = $term->taxonomy ?? '';

	if ( $max <= 1 || ! $term_id ) return;

	$list_type = get_term_meta($term_id, 'swell_term_meta_list_type', true) ?: 'card';
	?>
	<script>
	document.addEventListener('DOMContentLoaded', function () {
		const $ul = document.querySelector('#main_content .p-archiveContent .p-postList, #main_content .p-termContent .p-postList');
		if (!$ul) return;

		const btn = document.createElement('button');
		btn.type = 'button';
		btn.id = 'load-more';
		btn.className = 'swell-block-button__link';
		btn.dataset.paged = <?php echo $paged + 1; ?>;
		btn.dataset.termId = '<?php echo esc_js( $term_id ); ?>';
		btn.dataset.taxonomy = '<?php echo esc_js( $taxonomy ); ?>';
		btn.dataset.maxPages = '<?php echo esc_js( $max ); ?>';
		btn.dataset.listType = '<?php echo esc_js( $list_type ); ?>';
		btn.innerHTML = '<span>もっと見る</span>';

		const wrapper = document.createElement('div');
		wrapper.className = 'swell-block-button is-style-btn_normal';
		wrapper.appendChild(btn);
		$ul.parentNode.appendChild(wrapper);
	});
	</script>
	<?php
}

// Ajax 処理の登録
add_action('wp_ajax_load_more_posts', 'my_load_more_posts');
add_action('wp_ajax_nopriv_load_more_posts', 'my_load_more_posts');
function my_load_more_posts() {
	$paged     = isset($_POST['paged']) ? absint($_POST['paged']) : 1;
	$taxonomy  = sanitize_key($_POST['taxonomy'] ?? '');
	$term_id   = absint($_POST['term_id'] ?? 0);
	$list_type = sanitize_text_field($_POST['list_type'] ?? 'card');

	if ( ! in_array( $taxonomy, ['category', 'post_tag'], true ) || ! term_exists( $term_id, $taxonomy ) ) {
		wp_die('Invalid term', '', ['response' => 400]);
	}
	$list_type = in_array($list_type, ['card', 'list', 'list2', 'simple', 'big'], true) ? $list_type : 'card';
	$args = [
		'post_type'           => 'post',
		'posts_per_page'      => max(1, (int) get_option('posts_per_page')),
		'post_status'         => 'publish',
		'paged'               => $paged,
		'ignore_sticky_posts' => true,
	];

	if ( $taxonomy && $term_id ) {
		$args['tax_query'] = [[
			'taxonomy' => $taxonomy,
			'field'    => 'term_id',
			'terms'    => $term_id,
		]];
	}

	$list_args = [
		'type'  => $list_type,
		'paged' => $paged,
	];

	$query     = new WP_Query($args);
	$list_data = \SWELL_THEME\Parts\Post_List::get_list_data($list_args);

	if ( $query->have_posts() ) :
		while ( $query->have_posts() ) :
			$query->the_post();
			\SWELL_Theme::get_parts('parts/post_list/' . $list_data['parts_name'], $list_data['li_args']);
		endwhile;
	endif;

	wp_reset_postdata();
	wp_die();
}

add_action('wp_footer', 'my_load_more_inline_js');
function my_load_more_inline_js() {
	?>
	<script>
	document.addEventListener('DOMContentLoaded', function () {
		document.addEventListener('click', function (e) {
			const btn = e.target.closest('#load-more');
			if (!btn || btn.disabled) return;
			e.preventDefault();

			const list = document.querySelector('#main_content .p-archiveContent .p-postList, #main_content .p-termContent .p-postList');
			if (!list) return;

			let paged = parseInt(btn.dataset.paged, 10);
			const maxPages = parseInt(btn.dataset.maxPages, 10);
			const taxonomy = btn.dataset.taxonomy;
			const termId = btn.dataset.termId;
			const listType = btn.dataset.listType;

			if (paged > maxPages) return;

			btn.disabled = true;
			btn.setAttribute('aria-busy', 'true');
			const formData = new FormData();
			formData.append('action', 'load_more_posts');
			formData.append('paged', paged);
			formData.append('taxonomy', taxonomy);
			formData.append('term_id', termId);
			formData.append('list_type', listType);

			fetch('<?php echo esc_url( admin_url('admin-ajax.php') ); ?>', {
				method: 'POST',
				body: formData
			})
			.then(response => {
				if (!response.ok) throw new Error('HTTP ' + response.status);
				return response.text();
			})
			.then(html => {
				const temp = document.createElement('div');
				temp.innerHTML = html;
				const items = [...temp.children].filter(el => el.matches('li.p-postList__item'));
				if (!items.length) throw new Error('記事を取得できませんでした');
				items.forEach(li => list.appendChild(li));
				paged++;
				btn.dataset.paged = paged;
				if (paged > maxPages) btn.style.display = 'none';
			})
			.catch(err => {
				console.error('読み込み失敗:', err);
				btn.textContent = '再試行する(下のページ番号でも移動できます)';
			}).finally(() => {
				btn.disabled = false;
				btn.removeAttribute('aria-busy');
			});
		});
	});
	</script>
	<?php
}

あとは確認しましょう。

以上です。

取得成功と失敗時の操作を確認する

スクロールできます
操作・条件結果
初期表示からEnterで追加3件から6件へ追加
次のEnterで最終ページへ合計7件となりボタンを非表示
HTTP 503を返す通信失敗3件のまま再試行ボタンを有効に戻す
失敗後にページ番号2をEnter通常のページ遷移で4〜6番目の記事を表示
自動版のボタン付近へ移動4〜6番目の記事を自動追加

追加記事のIDが重複しないことを確認しました。

2026年10月3日にタグ一覧の手動・自動追加と最終ページを追加確認しました。

記事の追加による途中の並び替えと、他のプラグインとの組み合わせは未検証です。

自動無限スクロールに変更する方法

上のmy_load_more_inline_js関数とそのadd_actionだけを、下の組み合わせへ置き換えます。同名関数を両方登録しないでください。ボタンは手動でも操作できます。

add_action('wp_footer', 'my_load_more_inline_js');
function my_load_more_inline_js() {
	?>
	<script>
	document.addEventListener('DOMContentLoaded', function () {
		const btn = document.getElementById('load-more');
		const list = document.querySelector('#main_content .p-archiveContent .p-postList, #main_content .p-termContent .p-postList');
		if (!btn || !list) return;

		let paged = parseInt(btn.dataset.paged, 10);
		const maxPages = parseInt(btn.dataset.maxPages, 10);
		const taxonomy = btn.dataset.taxonomy;
		const termId = btn.dataset.termId;
		const listType = btn.dataset.listType;

		// IntersectionObserverで監視
		const observer = new IntersectionObserver((entries) => {
			entries.forEach(entry => {
				if (entry.isIntersecting && paged <= maxPages) {
					loadMorePosts();
				}
			});
		});

		observer.observe(btn); // 「もっと見る」ボタンを監視対象に

		btn.addEventListener('click', loadMorePosts);
		function loadMorePosts() {
			if (btn.disabled || paged > maxPages) return;
			btn.disabled = true;
			btn.setAttribute('aria-busy', 'true');
			const formData = new FormData();
			formData.append('action', 'load_more_posts');
			formData.append('paged', paged);
			formData.append('taxonomy', taxonomy);
			formData.append('term_id', termId);
			formData.append('list_type', listType);

			fetch('<?php echo esc_url( admin_url('admin-ajax.php') ); ?>', {
				method: 'POST',
				body: formData
			})
			.then(response => {
				if (!response.ok) throw new Error('HTTP ' + response.status);
				return response.text();
			})
			.then(html => {
				const temp = document.createElement('div');
				temp.innerHTML = html;
				const items = [...temp.children].filter(el => el.matches('li.p-postList__item'));
				if (!items.length) throw new Error('記事を取得できませんでした');
				items.forEach(li => list.appendChild(li));
				paged++;
				btn.dataset.paged = paged;
				if (paged > maxPages) {
					btn.style.display = 'none';
					observer.unobserve(btn);
				}
			})
			.catch(err => {
				console.error('読み込み失敗:', err);
				btn.textContent = '再試行する(下のページ番号でも移動できます)';
				observer.unobserve(btn);
			}).finally(() => {
				btn.disabled = false;
				btn.removeAttribute('aria-busy');
			});
		}
	});
	</script>
	<?php
}

追加取得の件数は「表示設定」の最大投稿数に合わせます。別のコードでメインクエリの件数・並び順・対象投稿を変えているサイトには、このまま適用しないでください。元へ戻すときは追加した関数とフックを削除(またはスニペットを無効化)し、表示件数を変更した場合は元の値に戻します。

2026年10月2日にWordPress 7.1.2 / SWELL 2.19.0の隔離カテゴリー一覧で、実際のHTTP取得とボタンのEnter操作を確認しました。

タグ一覧でボタンが出ない場合と最終ページの確認

条件確認結果
タグ一覧・手動Enterで3件→6件→7件、最後にボタンを非表示
タグ一覧・自動ボタンの位置までスクロールして6件、さらに進んで7件
カテゴリー一覧追加操作で3件→6件

SWELL 2.19.0のカテゴリー・タグ一覧では、記事リストが.p-termContentの中に出力されます。

掲載コードは、ボタン作成・手動追加・自動追加の取得先として.p-archiveContentと.p-termContentの両方に対応します。

自動版は手動版のmy_load_more_inline_jsを置き換えて使い、二つの同名関数を同時に有効にしません。

2026年10月3日、隔離したWordPress 7.1.2 / SWELL 2.19.0で、通常投稿7件を同じタグに登録し、1ページの表示を3件にして確認しました。

手動・自動とも1〜7の記事が重複せずに並び、最終ページを追加した後はボタンが非表示になりました。

導入先でも記事件数、重複、最終ボタン、残したページ番号のリンクを確認します。

不具合が出た場合は追加したPHPを停止して通常のページ送りへ戻し、キャッシュ更新後の一覧で確認してください。

閲覧途中の投稿増減、最適化プラグイン併用、この修正後のスマホ操作は未検証です。

タグ一覧で追加した最後の記事7とその前の記事5と6が表示される実画面
タグ一覧で7件目まで追加した後の末尾。最終追加後にボタンは非表示。2026年10月3日、隔離環境で撮影。
  • URLをコピーしました!

実装を任せたい方へ

SWELLのカスタマイズ代行

記事のコードをそのまま使うのが不安な場合や、サイトに合わせた調整が必要な場合は当社が実装します。1時間 ¥8,000〜(税込)。テーマ更新に強い書き方で納品します。

WAZAの有料記事のサブスクリプションも開始しました。

サービス

Service

WordPressサイトのカスタマイズのサービスに関心がありましたら、ぜひ詳細をご覧ください。

目次