WordPressのCSSコードを削減してメンテナンスしやすくする奥の手

ウェブページの表示速度を上げる一つの方法として、ファイルやコードの量を削減するというのが考えられます。

WordPressの場合、エディタやウィジェットといった便利な機能がありますが、これらで生成されるHTMLコードに適用するCSSコードは多くなりがちです。

今回は、WordPressで作られたウェブサイト (ブログ) のCSSコードを削減するための「奥の手」を紹介します。

ブロックエディタの登場で増えたCSSコード

ブロックエディタの登場により、それ以前のCSSコードに加え、ブロックエディタ用のCSSコードが上乗せされるカタチでCSSコードの量が増加しました。

クラシックエディタ (TinyMCEを使った旧エディタ) のCSSコードをごっそり削除できればいいのですが、ブロックエディタでクラシックエディタが使える「クラシックブロック」があり、ブロックエディタよりもクラシックエディタの方が使いやすいというユーザーも一定数いるため、削除できません。

また、ブロックエディタには、ウィジェットで表示させてきた内容を表示するブロックが用意されています。

  • アーカイブブロック (アーカイブウィジェット)
  • カレンダーブロック (カレンダーウィジェット)
  • カテゴリーブロック (カテゴリーウィジェット)
  • 最新のコメントブロック (最近のコメントウィジェット)
  • 最新の投稿ブロック (最近の投稿ウィジェット)
  • 固定ページリストブロック (固定ページウィジェット)
  • RSSブロック (RSSウィジェット)
  • タグクラウドブロック (タグクラウドウィジェット)
  • 検索ブロック (検索ウィジェット)

これらのブロックで出力されるHTMLコードの多くは、ウィジェットで出力されるHTMLコードとは異なるため、それぞれに専用のCSSコードが必要になります。

それに加え、デザインの一貫性を保つために、異なるHTMLコードを同じように表示させる工夫も必要になります。

しかし、ブロックエディタは頻繁に更新され、HTMLコードやCSSコードも度々変更されるため、表示の不具合が発生しやすくなり、メンテナンス性は良いとは言えません。

CSSコードを削減するための「奥の手」とは

奥の手とは「ブロックエディタありきでWordPressテーマを作る」ということです。

具体的には、クラシックエディタやウィジェットで出力されるHTMLコードを、ブロックエディタで出力するHTMLコードに統一します。

こうすることで、ブロックエディタ用のCSSコードに一本化でき、メンテナンスもしやすくなります。

クラシックエディタをブロックエディタに合わせる

例えば、WordPressテーマによっては会話形式の表現ができる「吹き出し」が用意されている場合があります。

通常、制作者が独自に作ったHTMLコードとCSSコードによって実現しますが、これをブロックエディタのHTMLコードに置き換えます。

<!-- WordPressテーマ「Cocoon」の吹き出し用HTMLコード -->
<div class="speech-wrap">
  <div class="speech-person">
    <figure class="speech-icon">
      <img class="speech-icon-image" … />
    </figure>
  </div>
  <div class="speech-balloon">
    <p> … </p>
  </div>
</div>
<!-- ブロックディタで作る吹き出し用HTMLコード -->
<div class="wp-block-columns">
  <div class="wp-block-column">
    <figure class="wp-block-image is-style-rounded">
      <img … />
    </figure>
  </div>
  <div class="wp-block-column">
    <div class="wp-block-group is-style-my-balloon">
      <div class="wp-block-group__inner-container">
        <p> … </p>
      </div>
    </div>
  </div>
</div>

こうすることで、制作者が独自にHTMLコードやCSSコードを作る必要がなくなり、CSSコードが削減できるだけでなく、クラシックエディタとブロックエディタを問わず「吹き出し」が使用できるようになります。
※実際には「グループブロック (wp-block-group)」に「is-style-my-balloon」が追加できるようにするなどのカスタマイズが必要になります。

この他に、文字色や背景色を変更するためのクラス属性値を、ブロックエディタで使用される「has-○○-color」や「has-○○-background-color」に統一するなども有効です。

ウィジェットをブロックエディタに合わせる

先程も挙げた通り、ブロックエディタにはウィジェットとほぼ同じ内容を表示するために、次のブロックが用意されています。

  • アーカイブブロック (アーカイブウィジェット)
  • カレンダーブロック (カレンダーウィジェット)
  • カテゴリーブロック (カテゴリーウィジェット)
  • 最新のコメントブロック (最近のコメントウィジェット)
  • 最新の投稿ブロック (最近の投稿ウィジェット)
  • 固定ページリストブロック (固定ページウィジェット)
  • RSSブロック (RSSウィジェット)
  • タグクラウドブロック (タグクラウドウィジェット)
  • 検索ブロック (検索ウィジェット)

ウィジェットのHTMLコード生成部分を、これらブロックのHTMLコード生成部分に置き換えることで、HTMLコードを統一します。

カレンダーウィジェットをカレンダーブロックに合わせる

カレンダーウィジェットとカレンダーブロックは、共にget_calendar関数を使用しているので、出力されるHTMLコードはほぼ同じです。 table要素を包含するdiv要素のクラス属性値が異なるので、そこに注意してCSSコードを記述していけば問題ありません。

/* カレンダーブロックとカレンダーウィジェットの両方に適用するスタイル */
table.wp-calendar-table { … }
nav.wp-calendar-nav { … }

/* カレンダーブロックにのみ適用するスタイル */
div.wp-block-calendar table.wp-calendar-table { … }
div.wp-block-calendar nav.wp-calendar-nav { … }

/* カレンダーウィジェットにのみ適用するスタイル */
div.calendar_wrap table.wp-calendar-table { … }
div.calendar_wrap nav.wp-calendar-nav { … }

検索ウィジェットを検索ブロックに合わせる

検索ウィジェットと検索ブロックは出力されるHTMLコードが異なるものの、ウィジェットではget_search_form関数を呼び出しているだけなので、テンプレート (searchform.php) か、get_search_formフックで、ブロックエディタと同一のHTMLコードが出力されるようにカスタマイズします。

次のPHPコードは、get_search_formフックでカスタマイズした例です。ブロックエディタで使用されるコメントタグ (<!-- wp:search {…} /-->) をdo_blocks関数の引数に指定して、検索ブロックと同じHTMLコードを出力します。

add_filter( 'get_search_form', 'my_search_form', 10, 2 );
if( !function_exists( 'my_search_form' ) ):
function my_search_form( $form, $args ) {
  $form = do_blocks( '<!-- wp:search { "label":"サイト内検索", "showLabel":false, "placeholder":"検索キーワードを入力", "width":100, "widthUnit":"%", "buttonText":"検索", "buttonPosition":"button-inside", "buttonUseIcon":true } /-->' );
  return $form;
}
endif;

その他のウィジェットをブロックエディタに合わせる

ウィジェットとブロックエディタで出力されるHTMLコードが異なり、検索ウィジェットのように一発でブロックエディタと同一の内容に置き換えられるフック等がない場合は、ウィジェットのphpクラス (WP_Widget_○○) をオーバーライド (extends) してカスタマイズしていきます。

ウィジェットとブロックエディタの設定項目が異なる場合

固定ページウィジェットと固定ページリストブロックが該当します。

固定ページウィジェットには、並び順 (sortby) と 除外ページ (exclude) の設定項目がありますが、固定ページリストブロックには、これらの設定項目がありません。

このような場合は、ブロックエディタと同じ構造になるよう、地道に整形していきます。

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_pages/
 */
// `WP_Widget_Pages`をオーバーライドして`My_Widget_Pages`を作る
if( !class_exists( 'My_Widget_Pages' ) ):
class My_Widget_Pages extends WP_Widget_Pages
{

  // $argsにはregister_sidebar関数で設定した情報が配列で格納されている
  // $instanceには管理画面の「ウィジェット」で設定された情報が配列で格納されている
  public function widget( $args, $instance )
  {
    $pages_args = [
      'exclude' => '',
      'title_li' => '',
      'sort_column' => 'menu_order',
      'item_spacing' => 'discard',
    ];

    // 除外ページの設定
    if( !empty( $instance[ 'exclude' ] ) ) {
      $pages_args[ 'exclude' ] = $instance[ 'exclude' ];
    }

    // 並び替えの設定
    if( !empty( $instance[ 'sortby' ] ) ) {
      $pages_args[ 'sort_column' ] = ( 'menu_order' === $instance[ 'sortby' ] ) ? 'menu_order,post_title' : $instance[ 'sortby' ];
    }

    // 固定ページ一覧を取得
    $pages_args = apply_filters( 'widget_pages_args', $pages_args, $instance );
    $pages_args[ 'echo' ] = false;
    $pages = wp_list_pages( $pages_args );

    // 固定ページ一覧が無ければ終了
    if( empty( $pages ) ) {
      return;
    }

    // 固定ページ一覧を固定ページリストブロックとほぼ同じHTMLコードに整形
    $replaces = [
      ' page_item_has_children' => ' has-child',
      ' current_page_ancestor' => '',
      ' current_page_parent' => '',
      ' current_page_item' => '',
      '<li class="page_item page-item-' => '<li class="wp-block-pages-list__item wp-block-pages-list__item-',
      '<a ' => '<a class="wp-block-pages-list__item__link" ',
      '<ul class=\'children\'>' => '<span class="wp-block-page-list__submenu-icon"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" role="img" aria-hidden="true" focusable="false"><path d="M1.50002 4L6.00002 8L10.5 4" stroke-width="1.5"/></svg></span><ul class="submenu-container">',
    ];
    $pages = str_replace( array_keys( $replaces ), array_values( $replaces ), $pages );
    $pages = '<ul class="wp-block-page-list">'. $pages. '</ul>';

    // ウィジェットのタイトルを取得
    $title = ( empty( $instance[ 'title' ] ) ) ? '' : $instance[ 'title' ];
    $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );

    // nav要素の追加
    $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
    $format = apply_filters( 'navigation_widgets_format', $format );
    if( 'html5' === $format ) {
      $aria_label = trim( strip_tags( $title ) );
      if( '' !== $aria_label ) {
        $aria_label = ' aria-label="'. esc_attr( $aria_label ). '"';
      }
      $pages = '<nav role="navigation"'. $aria_label. '>'. $pages. '</nav>';
    }

    // ウィジェットを出力
    if( '' !== $title ) {
      $title = $args[ 'before_title' ]. $title. $args[ 'after_title' ];
    }
    echo $args[ 'before_widget' ]. $title. $pages. $args[ 'after_widget' ];
  }

}
endif;

// `WP_Widget_Pages`を登録解除して
// `WP_Widget_Pages`をオーバーライド (extends) した`My_Widget_Pages`を登録
add_action( 'widgets_init', 'my_widget_pages', 10 );
if( !function_exists( 'my_widget_pages' ) ):
function my_widget_pages() {
  unregister_widget( 'WP_Widget_Pages' );
  register_widget( 'My_Widget_Pages' );
}
endif;

ウィジェットとブロックエディタの設定項目がほぼ同じ場合

下記のブロックが該当します。

  • アーカイブブロック (アーカイブウィジェット)
  • カテゴリーブロック (カテゴリーウィジェット)
  • 最新のコメントブロック (最近のコメントウィジェット)
  • 最新の投稿 (最近の投稿ウィジェット)
  • RSSブロック (RSSウィジェット)
  • タグクラウドブロック (タグクラウドウィジェット)

検索ウィジェットと同様に、ブロックエディタで使用されるコメントタグ (<!-- wp:○○ {…} /-->) をdo_blocks関数の引数に指定して、ブロックエディタと同じHTMLコードを出力します。

例として、アーカイブウィジェットのオーバーライド方法のみを紹介しますが、他のウィジェットも同様の方法でブロックエディタに合わせられます。

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_archives/
 */
// `WP_Widget_Archives`をオーバーライドして`My_Widget_Archives`を作る
if( !class_exists( 'My_Widget_Archives' ) ):
class My_Widget_Archives extends WP_Widget_Archives
{

  // $argsにはregister_sidebar関数で設定した情報が配列で格納されている
  // $instanceには管理画面の「ウィジェット」で設定された情報が配列で格納されている
  public function widget( $args, $instance )
  {
    // ドロップダウンで表示するかどうか
    $dropdown = ( empty( $instance[ 'dropdown' ] ) ) ? 'false' : 'true';

    // 投稿数を表示するかどうか
    $count = ( empty( $instance[ 'count' ] ) ) ? 'false' : 'true';

    // アーカイブブロックのHTMLコードを取得
    $archives = do_blocks( '<!-- wp:archives { "displayAsDropdown":'. $dropdown. ', "showPostCounts":'. $count. ' } /-->' );

    // アーカイブが無ければ終了
    if( empty( $archives ) ) {
      return;
    }

    // ウィジェットのタイトルを取得
    $title = ( !empty( $instance[ 'title' ] ) ) ? $instance[ 'title' ] : '';
    $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );

    // nav要素の追加
    if( 'false' === $dropdown ) {
      $format = ( current_theme_supports( 'html5', 'navigation-widgets' ) ) ? 'html5' : 'xhtml';
      $format = apply_filters( 'navigation_widgets_format', $format );
      if( 'html5' === $format ) {
        $aria_label = trim( strip_tags( $title ) );
        if( '' !== $aria_label ) {
          $aria_label = ' aria-label="'. esc_attr( $aria_label ). '"';
        }
        $archives = '<nav role="navigation"'. $aria_label. '>'. $archives. '</nav>';
      }
    }

    // ウィジェットを出力
    if( '' !== $title ) {
      $title = $args[ 'before_title' ]. $title. $args[ 'after_title' ];
    }
    echo $args[ 'before_widget' ]. $title. $archives. $args[ 'after_widget' ];
  }

}
endif;

// `WP_Widget_Archives`を登録解除して
// `WP_Widget_Archives`をオーバーライド (extends) した`My_Widget_Archives`を登録
add_action( 'widgets_init', 'my_widget_archives', 10 );
if( !function_exists( 'my_widget_archives' ) ):
function my_widget_archives() {
  unregister_widget( 'WP_Widget_Archives' );
  register_widget( 'My_Widget_Archives' );
}
endif;

他のブロックのHTMLコードを取得するためのPHPコードは次のようになります。カスタマイズする際の参考にしてください。

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_categories/
 */
// displayAsDropdown, showHierarchy, showPostCountsの値をそれぞれ
// $instance[ 'dropdown' ], $instance[ 'hierarchical' ], $instance[ 'count' ]の値に従って変更
$categories = do_blocks( '<!-- wp:categories { "displayAsDropdown":true, "showHierarchy":true, "showPostCounts":true } /-->' );

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_recent_comments/
 */
// commentsToShowの値を
// $instance[ 'number' ]の値に変更
// その他の設定はお好みで
$comments = do_blocks( '<!-- wp:latest-comments { "commentsToShow":8, "displayAvatar":true, "displayDate":true, "displayExcerpt":true } /-->' );

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_recent_posts/
 */
// postsToShow, displayPostDateの値をそれぞれ
// $instance[ 'number' ], $instance[ 'show_date' ]の値に従って変更
// その他の設定はお好みで
$posts = do_blocks( '<!-- wp:latest-posts { "postsToShow":8, "displayPostDate":true, "displayAuthor":true, "displayPostContent":true, "displayFeaturedImage":true, "featuredImageAlign":"right", "featuredImageSizeWidth":160, "featuredImageSizeHeight":160, "addLinkToFeaturedImage":true, "postLayout":"grid", "columns":2 } /-->' );

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_rss/
 */
// feedURL, itemsToShow, displayExcerpt, displayAuthor, displayDateの値をそれぞれ
// $instance[ 'url' ], $instance[ 'items' ], $instance[ 'show_summary' ], $instance[ 'show_author' ], $instance[ 'show_date' ]の値に従って変更
// その他の設定はお好みで
$rss = do_blocks( '<!-- wp:rss { "feedURL":"", "itemsToShow":8, "displayExcerpt":true, "displayAuthor":true, "displayDate":true, "blockLayout":"grid", "columns":2 } /-->' );

/**
 * @link https://developer.wordpress.org/reference/classes/wp_widget_tag_cloud/
 */
// taxonomy, showTagCountsの値をそれぞれ
// $instance[ 'taxonomy' ], $instance[ 'count' ]の値に従って変更
$tags = do_blocks( '<!-- wp:tag-cloud { "taxonomy":"post_tag", "showTagCounts":true } /-->' );

WordPressさん、大変お世話になっております。が、割とカオスなのが玉に瑕。

関連記事