WordPressのメディア型サイトを作る際、カテゴリー毎の記事一覧をタブ切り替えで表示する実装に頻繁に出くわします。
デイトラWeb制作コースでも納期設定のある課題に採用されていて、様々な挑戦者を苦しめていました。
そこで今回は、非同期でカテゴリーを切り替えられる実装をご紹介します。カテゴリータブ機能は使いまわしできると、Web制作案件で一気に自分の時給をあげられる美味しいポイントです。
コードの解説も入れているので、自分用のNotionコードスニペットにぜひ溜めてもらえればと思います。
この記事を書いたのは
しょーご(@samurabrass)
当ブログ「しょーごログ」の運営者。2018年からWeb制作・フロントエンドエンジニアとして主にWordPressでのサイト制作やシステム開発のフロントエンドを担当。同時にブログとYouTubeで情報発信を行っている。駆け出しエンジニアのコーディング課題添削も行い、スクール講師を4年以上している経験を活かした分かりやすい記事制作を心がけている。
WordPressカテゴリータブの完成見本
今回実装するカテゴリータブ切り替えの見本が以下になります。
まず「新着記事」のタブは、新規記事をカテゴリー関係なく全記事出力します。
その横にカテゴリー毎のタブが並んでいて、クリックするとカテゴリー内の記事一覧がそれぞれ出力されます。
またタブ切り替え時に非同期で切り替わるようになっています。
カテゴリータブの実装方法解説
全体概要
今回触るファイルは以下のみです。
- functions.php
- index.php(一覧を出したい任意のファイルでOK)
- css
- js
その他のファイルについてはそれぞれの環境があるかと思いますので、特に触れません。
functions.php
functions.phpの責務は以下です。
- ショートコード[category_tabs]の定義と実装
- 記事一覧を取得してHTML形式で出力する関数get_posts_list()の定義
- カテゴリータブとそれに対応する記事一覧を生成する機能の提供
つまり、HTML含め記事一覧の出力を司る大元が、このfunctions.phpに記載されています。
以下のコードをコピペしてください。
// ショートコード関数
function category_tabs_shortcode($atts) {
$atts = shortcode_atts(array(
'posts_per_page' => 8,
), $atts);
$categories = get_categories(array('hide_empty' => 1));
$output = '<div class="category-tabs">';
$output .= '<ul class="tab-nav">';
// 「新着記事」タブ
$output .= '<li class="active"><a href="#tab-recent">新着記事</a></li>';
// 各カテゴリータブ
foreach ($categories as $category) {
$output .= '<li><a href="#tab-' . $category->term_id . '">' . $category->name . '</a></li>';
}
$output .= '</ul>';
$output .= '<div class="tab-content">';
// 新着記事の一覧(全記事を表示)
$output .= '<div id="tab-recent" class="tab-pane active">';
$output .= get_posts_list(array('posts_per_page' => -1, 'post_type' => 'post'));
$output .= '</div>';
// 各カテゴリーの記事一覧
foreach ($categories as $category) {
$output .= '<div id="tab-' . $category->term_id . '" class="tab-pane">';
$output .= get_posts_list(array('cat' => $category->term_id, 'posts_per_page' => $atts['posts_per_page']));
$output .= '</div>';
}
$output .= '</div></div>';
return $output;
}
add_shortcode('category_tabs', 'category_tabs_shortcode');
function get_posts_list($args) {
$query = new WP_Query($args);
$output = '';
$count = 0;
if ($query->have_posts()) {
$output .= '<div class="post-cards">';
while ($query->have_posts() && $count < 8) {
$query->the_post();
$output .= '<div class="post-card">';
if (has_post_thumbnail()) {
$output .= '<div class="post-thumbnail">' . get_the_post_thumbnail(null, 'medium') . '</div>';
} else {
$output .= '<div class="post-thumbnail"><img src="' . get_template_directory_uri() . '/images/no-image.png" alt="No Image Available"></div>';
}
$output .= '<div class="post-content">';
$output .= '<h2 class="post-title"><a href="' . get_permalink() . '">' . get_the_title() . '</a></h2>';
$output .= '<div class="post-excerpt">' . wp_trim_words(get_the_excerpt(), 20, '...') . '</div>';
$output .= '</div>'; // .post-content
$output .= '</div>'; // .post-card
$count++;
}
$output .= '</div>'; // .post-cards
} else {
$output .= '<p>記事が見つかりません。</p>';
}
wp_reset_postdata();
return $output;
}
コードを塊ごとに解説していきます。
ショートコード関数 category_tabs_shortcode
この関数は、カテゴリータブ全体の構造を生成しています。
- ショートコードの属性を設定(デフォルトで1ページあたり8投稿)
- カテゴリーのリストを取得
- タブのナビゲーション(ul要素)を生成
- 「新着記事」タブを追加
- 各カテゴリーのタブを追加
- タブのコンテンツ(div要素)を生成
- 「新着記事」タブの内容を追加(全投稿を表示)
- 各カテゴリータブの内容を追加(カテゴリーごとに投稿を表示)
投稿リスト生成関数 get_posts_list
この関数は、各タブ内の投稿リストを生成しています。
- WP_Queryを使用して投稿を取得
- 最大8件の投稿をループで処理
- 各投稿に対して:
- サムネイル画像(ない場合はno-image)を表示
- 投稿タイトルを表示
- 投稿の抜粋(最大20単語)を表示
- 各投稿に対して:
- 投稿がない場合はメッセージを表示
index.php
index.phpでやっていることは以下です。
- ブログのメインページのレイアウトと構造の定義
- [category_tabs]ショートコードの呼び出しによるカテゴリータブの表示
- 投稿ループの実行と各投稿の表示
基本的にfunctions.phpで定義したショートコードを呼び出しているのみです。
<div id="primary" class="content-area">
<main id="main" class="site-main">
<?php
if ( have_posts() ) :
?>
<header>
<h1 class="page-title screen-reader-text"><?php single_post_title(); ?></h1>
</header>
<?php
// カテゴリータブを表示
echo do_shortcode('[category_tabs posts_per_page="8"]');
/* Start the Loop */
while ( have_posts() ) :
the_post();
/*
* Include the Post-Type-specific template for the content.
* If you want to override this in a child theme, then include a file
* called content-___.php (where ___ is the Post Type name) and that will be used instead.
*/
get_template_part( 'template-parts/content', get_post_type() );
endwhile;
the_posts_navigation();
else :
get_template_part( 'template-parts/content', 'none' );
endif;
?>
</main><!-- #main -->
</div><!-- #primary -->
カテゴリータブの表示
echo do_shortcode('[category_tabs posts_per_page="8"]');
カスタムのカテゴリータブショートコードを実行し、結果を表示します。1ページあたり8投稿を表示するよう設定しています。
投稿が無い場合の処理
get_template_part( 'template-parts/content', 'none' );
表示する投稿が無い場合、「コンテンツなし」用のテンプレートパーツを読み込みます(別途作成してください)
CSS
cssは特に解説することはありませんが、タイトルが二行以上にならないように配慮しています。CSS設計を厳密に行っている場合は、コピペ後にクラス名を変えてください。
.category-tabs {
margin-bottom: 20px;
}
.category-tabs .tab-nav {
list-style: none;
padding: 0;
margin: 0;
display: flex;
border-bottom: 1px solid #ccc;
flex-wrap: wrap;
}
.category-tabs .tab-nav li {
margin-right: 10px;
margin-bottom: -1px;
}
.category-tabs .tab-nav a {
display: block;
padding: 10px 15px;
text-decoration: none;
color: #333;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-bottom: none;
}
.category-tabs .tab-nav li.active a {
background-color: #fff;
border-bottom-color: #fff;
}
.category-tabs .tab-content {
border: 1px solid #ccc;
border-top: none;
padding: 15px;
}
.category-tabs .tab-pane {
display: none;
}
.category-tabs .tab-pane.active {
display: block;
}
.post-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
padding: 20px;
}
.post-card {
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
}
.post-thumbnail {
height: 200px;
overflow: hidden;
}
.post-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.post-content {
padding: 15px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.post-title {
margin: 0 0 10px;
font-size: 1.2em;
line-height: 1.3;
max-height: 2.6em; /* 2行分の高さ */
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.post-title a {
color: #333;
text-decoration: none;
}
.post-title a:hover {
color: #0066cc;
}
.post-excerpt {
font-size: 0.9em;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
flex-grow: 1;
}
JS(jQuery)
JSの責務は以下になります。
- カテゴリータブの動的な切り替え機能の実装
- ユーザーがタブをクリックしたときの挙動の定義
- アクティブなタブとそれに対応するコンテンツの表示制御
jQuery(document).ready(function($) {
$('.category-tabs .tab-nav a').on('click', function(e) {
e.preventDefault();
var target = $(this).attr('href');
$(this).parent().addClass('active').siblings().removeClass('active');
$(target).addClass('active').siblings().removeClass('active');
});
});
クリックされたリンクの href
属性の値を取得
var target = $(this).attr('href');
activeクラスの追加と削除
$(this).parent().addClass('active').siblings().removeClass('active');
$(target).addClass('active').siblings().removeClass('active');
activeクラスの定義はCSSで確認しておいてください。
記事カード内にカテゴリーやタグを入れる場合
記事カードの中に、カテゴリーやタグを出力する場合があるかと思います。
その時は以下のコードを入れてあげればOK。
// カテゴリーの表示
$categories = get_the_category();
if ($categories) {
$output .= '<div class="post-categories"><strong>カテゴリー:</strong> ';
foreach ($categories as $category) {
$output .= '<a href="' . get_category_link($category->term_id) . '">' . $category->name . '</a> ';
}
$output .= '</div>';
}
// タグの表示
$tags = get_the_tags();
if ($tags) {
$output .= '<div class="post-tags"><strong>タグ:</strong> ';
foreach ($tags as $tag) {
$output .= '<a href="' . get_tag_link($tag->term_id) . '">' . $tag->name . '</a> ';
}
$output .= '</div>';
}
厳密に行うなら、タグやカテゴリー数の出力上限を定めたほうがいいかもですし、
もし記事カード全体をリンクにするなら、記事カード内にリンクはいらない気もします(カードのaタグと被るので)
そこは臨機応変に組み替えてみてください。
宣伝:実務レベルのWordPress課題出しています
コーディング課題上級編では、実務レベルのWordPress課題に挑戦していただきます。
- プロのレビューあり
- 初稿提出は24日以内(努力目標)
- 修正点は10箇所以内(努力目標)
という目安を設けており、実案件さながらの緊張感の中で取り組むことが可能です。Web制作学習ロードマップの中では、最後の課題として設定しています。
Web制作を独学している方や、自分の実力を試したい方はぜひ挑戦してみてください。
超実践編では納期厳守の模擬案件を経験し、スキル面以外にコミュニケーションも徹底レビューを受けることができます。
最近は実案件のノウハウも多いですが、「納期が短い案件の中で、丁寧なコミュニケーションを本当に実践できますか?」
この課題では、極限まで実案件に近い状況で、発注者である私とコミュニケーションを取りながら、
- 見積書提出
- 実装→初稿提出
- レビュー→修正
- 再修正→納品
- 請求書
この流れを実践していただき、最後にzoomであなたに全体レビューを行います。
- 学習はだいたい終わったけど、納期までに納品できるか不安
- 中々継続と紹介で案件が回らない
このような中級者を飛躍させる超実践編、受講には条件がありますので、詳細はリンク先よりご確認下さい。
あなたの挑戦を待っています!!
\レビューを受けて圧倒的な自信を身につける!/
ご寄付を頂けると今後の更新の励みになります!