前回、WordPress のメニュー項目をアイコン表示させる方法を紹介した。今回はもう少し複雑なことをする。メニューでサイトホームにリンクさせるためにカスタムリンクを使用すると、サイトを引っ越した時に、再設定しなくてはならなくなる。また、アイコンとテキスト両方表示させた方が良い。これらを解決できる方法を編み出した。
初期状態
今回使用するテーマは Twenty Seventeen。Twenty Seventeen ではトップメニューとソーシャルリンクメニューの2種類作れる。で、次の画像のような感じで、その2種類のメニューを作成した状態を想定している。
この初期状態では、トップメニュー項目がテキストで、フッターのソーシャルリンクのアイコンがリンクアイコンになっている。
これを、トップメニューをアイコン + テキスト or アイコンのみ に変更し、ソーシャルリンクメニューのアイコンも指定できるようにし、ここでは RSS アイコンに変更する。
メニューの追加の方法は以前の記事を参考。今回のカスタマイズで、前回の PHP コード、CSS ルールは不要になるので、追加している場合は取り除いておく。
メニューの URL の文字列によってアイコンを指定
まず、タグやカテゴリー、静的ページをメニューに追加している場合、その URL の一部、例えば /about/
とか /contact/
が含まれる場合に、予め指定しておいたアイコンを使うようにさせる。
PHP コード
クラスを書いた。以下の PHP コードを functions.php
に追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
class TwentySeventeenTechnotes_CustomMenuIcons { public $aMapping = array( // sub-string => dash-icon id // '/wordpress/'=> 'wordpress-alt' ); public $aAllowedLocations = array( 'top', // 'social', ); public function __construct( array $aMapping=array(), array $aLocations=array( 'top', ) ) { $this->aMapping = $aMapping; $this->aAllowedLocations = $aLocations; add_filter( 'nav_menu_item_title', array( $this, 'replyToInsertIcons' ), 10, 4 ); } public function replyToInsertIcons( $sItemTitle, $oItem, $oArguments, $iDepth ) { if ( ! in_array( $oArguments->theme_location, $this->aAllowedLocations ) ) { return $sItemTitle; } foreach( $this->aMapping as $_sNeedle => $_sDashIconID ) { if ( false === strpos( $oItem->url, $_sNeedle ) ) { continue; } return "<span class='screen-reader-text'>" . $sItemTitle . "</span>" . "<svg viewBox='0 0 100 100' class='icon icon-{$_sDashIconID} menu-icon' role='img'>" . "<use xlink:href='" . esc_url( includes_url( "fonts/dashicons.svg#{$_sDashIconID}" ) ) . "'></use>" . "</svg>" . $sItemTitle; } return $sItemTitle; } } new TwentySeventeenTechnotes_CustomMenuIcons( array( '/wordpress/' => 'wordpress', '/note/' => 'welcome-write-blog', '/about/' => 'info', ) ); |
CSS ルール
次の CSS を style.css
に追加。テーマによって各々独自の修正が必要になる。ここでは Twenty Seventeen 用。
1 2 3 4 5 6 7 8 9 10 11 12 |
.main-navigation ul a > .icon.menu-icon { display: inline-block; font-size: 1.5em; margin-right: 0.2em; vertical-align: bottom; } @media screen and (min-width: 48em) { .main-navigation .menu-item-has-children > a > .icon.menu-icon { left: 0; margin-right: .4em; } } |
PHP クラスの使い方
クラスの使い方は、インスタンス化時に配列で URL にどの文字列が入っていたらどのアイコンを使うかを指定してあげる。
1 2 3 4 5 6 7 |
new TwentySeventeenTechnotes_CustomMenuIcons( array( '/wordpress/' => 'wordpress', '/note/' => 'welcome-write-blog', '/about/' => 'info', ) ); |
この場合、/wordpress/
という文字列がメニューの URL に入っていたら、wordpress
の Dashicon を使ってね、ということ。Dashicon の一覧はこちらから参照。リンクしたページでアイコンを選択して、Copy HTMLを押すと、次のような文字列がコピーされる。
1 |
<span class="dashicons dashicons-welcome-learn-more"></span> |
この、dashicons-welcome-learn-more
の dashicons-
を取り除いた、welcome-learn-more
が SVG ファイルに登録されているアイコン ID になっているため、これを指定する。上の '/note/' => 'welcome-write-blog',
は /note/
という文字列がメニューのリンクの URL に入っている場合は、 welcome-write-blog
のアイコンを使ってね、としている。
うまくいくとこんな感じで、メニューのラベルの前にアイコンが挿入される。
このコードではアイコンのみの設定は無いが、以下の
1 2 3 4 5 6 7 |
return "<span class='screen-reader-text'>" . $sItemTitle . "</span>" . "<svg viewBox='0 0 100 100' class='icon icon-{$_sDashIconID} menu-icon' role='img'>" . "<use xlink:href='" . esc_url( includes_url( "fonts/dashicons.svg#{$_sDashIconID}" ) ) . "'></use>" . "</svg>" . $sItemTitle; |
$sItemTitle
を抜いてあげて、このように変更すればよい。
1 2 3 4 5 6 |
return "<span class='screen-reader-text'>" . $sItemTitle . "</span>" . "<svg viewBox='0 0 100 100' class='icon icon-{$_sDashIconID} menu-icon' role='img'>" . "<use xlink:href='" . esc_url( includes_url( "fonts/dashicons.svg#{$_sDashIconID}" ) ) . "'></use>" . "</svg>"; |
関数を指定して動的 URL を適用する
次に、ホーム URL やフィードURLといったサイトを引っ越しすると再設定しなくてはならないような URL を引越し後も再設定不要にする。どういうことかというと、入力する URL にカスタムのクエリパラメータを入れて、そこから URL 生成の関数を指定する。更に、同様の方法でアイコンの指定、ラベルの表示の有無も制御する。
PHP コード
以下を functions.php
に追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
class TwentySeventeenTechnotes_CustomMenuLinks { public $aAllowedLocations = array( 'top' ); public $sEnablerURLQueryKey = 'icon'; private $___aQueryDefault = array( 'func' => '', 'params' => array(), 'icon' => '', 'show_title' => true, 'attr' => array(), ); public function __construct( array $aLocations=array( 'top' ), $sEnablerURLQueryKey='icon' ) { $this->sEnablerURLQueryKey = $sEnablerURLQueryKey; $this->aAllowedLocations = $aLocations; add_filter( 'walker_nav_menu_start_el', array( $this, 'replyToReplaceLinks' ), 10, 4 ); add_filter( 'nav_menu_css_class', array( $this, 'replyToModifyCSSClass' ), 10, 4 ); } public function replyToModifyCSSClass( $aCSSClasses, $oItem, $oArguments, $iDepth ) { if ( ! in_array( $oArguments->theme_location, $this->aAllowedLocations ) ) { return $aCSSClasses; } $_sQueryParameters = parse_url( $oItem->url, PHP_URL_QUERY ); parse_str( $_sQueryParameters, $aQuery ); if ( ! isset( $aQuery[ $this->sEnablerURLQueryKey ] ) ) { return $aCSSClasses; } $aQuery = $aQuery + $this->___aQueryDefault; $_aParams = isset( $aQuery[ 'params' ] ) ? ( array ) $aQuery[ 'params' ] : array(); $oItem->url = remove_query_arg( array_keys( $aQuery ), $oItem->url ); $oItem->_url = call_user_func_array( $aQuery[ 'func' ], $_aParams ); $oItem->_query = $aQuery; $oItem->current = $this->___isCurrentURL( $oItem->_url ); if ( $oItem->current ) { $oItem->classes[] = 'current-menu-item'; $aCSSClasses[] = 'current-menu-item'; } return $aCSSClasses; } /** * Converts ?icon={dash-icon id} menu link to an icon link. * * @param string $sItemOutput The menu item output. * @param WP_Post $oItem Menu item object. * @param integer $iDepth Depth of the menu. * @param object $oArguments wp_nav_menu() arguments. * @return string The menu item output with social icon. */ public function replyToReplaceLinks( $sItemOutput, $oItem, $iDepth, $oArguments ) { if ( ! isset( $oItem->_query ) ) { return $sItemOutput; } $aQuery = $oItem->_query; $_sURL = $oItem->_url; $_aAttributes = is_scalar( $aQuery[ 'attr' ] ) ? array( $aQuery[ 'attr' ] => '' ) : ( array ) $aQuery[ 'attr' ]; $_aAttributes = array( 'href' => $_sURL, ) + $_aAttributes; if ( $oItem->current ) { $_aAttributes[ 'aria-current' ] = 'page'; } if ( ! $aQuery[ 'icon' ] ) { return "<a " . $this->___getAttributes( $_aAttributes ) . ">" . $oItem->title . "</a>"; } $_sIconID = $aQuery[ 'icon' ]; return "<a " . $this->___getAttributes( $_aAttributes ) . ">" . "<span class='screen-reader-text'>" . $oItem->title . "</span>" . "<svg viewBox='0 0 100 100' class='icon icon-{$_sIconID} menu-icon' role='img'>" . "<use xlink:href='" . esc_url( includes_url( "fonts/dashicons.svg#{$_sIconID}" ) ) . "'></use>" . "</svg>" . ( $this->___isTrue( $aQuery[ 'show_title' ] ) ? $oItem->title : '' ) . "</a>"; } /** * @return boolean */ private function ___isTrue( $sValue ) { if ( 'false' === strtolower( $sValue ) ) { return false; } return ( boolean ) $sValue; } /** * @param array $aAttributes * @return string */ private function ___getAttributes( array $aAttributes ) { $_sAttributes = ''; foreach ( $aAttributes as $_sAttribute => $_sValue ) { if ( ! is_scalar( $_sValue ) ) { continue; } if ( '' === $_sValue ) { $_sAttributes .= ' ' . $_sAttribute; continue; } if ( false !== $_sValue ) { $_sValue = ( 'href' === $_sAttribute ) ? esc_url( $_sValue ) : esc_attr( $_sValue ); $_sAttributes .= ' ' . $_sAttribute . '="' . $_sValue . '"'; } } return $_sAttributes; } /** * @see _wp_menu_item_classes_by_context() */ private function ___isCurrentURL( $sURL ) { $sURL = untrailingslashit( $sURL ); $_sRootRelativeCurrent = untrailingslashit( $_SERVER[ 'REQUEST_URI' ] ); //if it is the customize page then it will strips the query var off the url before entering the comparison block. if ( is_customize_preview() ) { $_sRootRelativeCurrent = strtok( untrailingslashit( $_SERVER[ 'REQUEST_URI' ] ), '?' ); } $_sCurrentURL = set_url_scheme( 'http://' . $_SERVER[ 'HTTP_HOST' ] . $_sRootRelativeCurrent ); $_sIndexlessCurrent = untrailingslashit( preg_replace( '/' . preg_quote( $GLOBALS[ 'wp_rewrite' ]->index, '/' ) . '$/', '', $_sCurrentURL ) ); $_aMatches = array( $_sCurrentURL, urldecode( $_sCurrentURL ), $_sIndexlessCurrent, urldecode( $_sIndexlessCurrent ), $_sRootRelativeCurrent, urldecode( $_sRootRelativeCurrent ), ); return in_array( $sURL, $_aMatches ); } } new TwentySeventeenTechnotes_CustomMenuLinks( array( 'top', 'social' ) ); |
CSS ルール
CSS ルールは上のを使いまわせばよい。テーマによって CSSは違うので、Twenty Seventeen 以外のテーマでは独自に書く必要がある。
PHP クラスの使い方
- インスタンス化時に、配列で、このカスタマイズを適用させる
theme_location
プロパティを指定する。このプロパティの値はテーマによって違うので自分で調べるしか無い。Twenty Seventeen の場合はtop
とsocial
の2種類だった。 - 次のアーギュメントをメニュー追加時の URL クエリパラメーターで渡してやる。
func
: URL を生成させるPHP 関数-
params
: 上のfunc
で指定した関数に渡す引数。¶ms[]=foo&prams[]=bar
のようにして配列で渡すことが可能。 icon
: Dashicon の SVG ファイルで定義付けされている ID。クラスがdashicons-welcome-learn-more
ならwelcome-learn-more
の部分。show_title
: ラベルを表示するかどうかattr
:<a>
タグの属性。例:&attr[target]=_blank
カスタマイザーからメニューの編集
上の PHPコードが追加できたら、カスタマイザーからメニューの URL を編集する。
例として、サイトのホーム URL を取得するには get_home_url()
という WordPress の関数が使える。これをメニューの URL のクエリパラメータに指定する。http://{サイトのホームアドレス}?func=get_home_url
で、PHP 側でメニューがレンダリングされる時に、get_home_url()
が呼び出され、その値がリンクの href
に適用される。
そして、アイコンも指定したい。ホームアイコンには Dashicon の admin-home
がよさげ。なので、さっきの続きで、 http://{サイトのホームアドレス}?func=get_home_url&icon=admin-home
としてやる。
うまく行けば、カスタマイザーはリアルタイムでメニューをアップデートしてくれ、Home のラベルの前にアイコンを挿入してくれる。
同様に、次は RSS フィードのメニュー。URL の最後に ?func=get_feed_link&icon=rss&show_title=0&attr[target]=blank
を追加。get_feed_link()
はサイトのフィード URL を取得してくれる。show_title
に 0 を渡して、ラベルを非表示にしている。
最後に、ソーシャルリンクメニューの RSS の URL の最後も同様に ?func=get_feed_link&icon=rss&show_title=0&attr[target]=blank
を追加してやる。すると、リンクアイコンがフィードアイコンに変化する。
結果
全てうまくいくとこんな感じになる。
今回は Twenty Seventeen で行った。他のテーマでは試してないけど、PHP のコード自体は動くはず。ただ、CSS はテーマごとに調節する必要がある。
おまけ
ちょっと CSS を追加で調節したいなと思い、以下を追加した。
1 2 3 4 5 6 7 8 |
@media screen and (min-width: 48em) { .main-navigation a { padding: 1em; } .main-navigation .menu-item-has-children > a > .icon.icon-angle-down { margin-right: -3px; } } |