よくあるグローバルナビのCSSをじっくりコーディング

WEBサイト制作でよくある見た目のグローバルナビのHTMLとCSSをコーディングした際、考えることが予想よりも多くありました。コーディングの流れをじっくり解説します。

はじめに

グローバルナビに以下のようなデザインが指定されているウェブサイトのHTMLとCSSのコーディングを行なっていました。(デザインは仮のものです)

完成形

よく見る形だし、さくっと組めるかなと着手前は考えたのですが、やってみたら予想よりも考えなきゃいけないことが多くありました。CSSの良い復習にもなったので、自分へのメモも兼ねて、コーディングの流れをじっくり解説します。

なお、フレックスボックス(display: flex)を多用したコーディングを解説しますので、対応ブラウザにはご注意ください。

デザイン仕様の確認

コーディングを始める前に、デザインカンプからコーディングに必要な情報を抜き出して整理します。

項目の横幅

まず、項目ひとつのデザインを確認します。

項目の横幅

項目の横幅は、文字数によって可変のようです。そして、もっとも文字数の多い6文字の項目でも、文字と左右の境界線との間には、最低20pxの余白があります。文字数が少ない場合、この余白が20pxよりも広くなっています。

リンクとホバーの領域

またデザインカンプでは表現されていませんが、操作のしやすさを考えて、リンク領域の幅は境界線から境界線まで、高さは帯の高さいっぱいまでとします。

マウスホバーした際に下線が表示されるデザインですが、この下線はリンク領域いっぱいでなく、文字数の横幅の分だけの長さを持つ点に注意します。

項目の境界線

項目と項目の間の境界線は、項目が2行の場合でも高さは変化せず、常に帯の中心の高さにいます。最初の項目の前と、最後の項目の後には境界線は表示しません。

グローバルナビ全体の横幅について考えます。

全体の横幅

上にあるヘッダーや、下にあるメインビジュアルは幅980pxで作られており、グローバルナビも端を揃えてデザインされています。しかし、グローバルナビの項目の文字の端がそろっているため、実際には上の図のように、リンク領域がはみ出るように組んだ方が良さそうだと考えました。

最初と最後の項目のクリック領域を端に合わせて削ってしまうよりは、他の項目と同じように文字の左右にもクリック可能な余白があることで、操作感が良くなると思います。デザインカンプに表現されていない部分ではあるため、デザイナーにすぐ確認できる環境であれば確認した方が良いでしょう。

HTML設計

デザイン仕様が確認できたので、次にHTML構造を考えていきます。

まずいちばん外側のHTMLから、以下のような構造を作ります。

nav (帯の背景色や装飾)
┗ div (横幅980pxを定める)
 ┗ ul

グローバルナビのHTML設計

内側のul要素が、親のdiv要素を突き抜けています。これはデザイン仕様の、リンク領域を削ることなく文字の端を揃えるためのもので、詳細は後ほど解説します。

ul以下の構造は以下のようにします。

ul
┗ li
┃┗ a
┃ ┗ span(ホバー時に文字の長さの分だけ下線を引くために使う)
┗ li
┃┗ a
┃ ┗ span
(以下、項目の数だけ繰り返し)

項目のHTML設計

コーディング

それでは実際にHTMLとCSSをコーディングします。

この記事ではできるだけレイアウトに限った話だけにしたいので、Reset.cssを読み込んでブラウザのデフォルトスタイルを無効にしています。

完成形をCodePenで見る

nav〜ulのコーディング

まず、一番外側からul要素までのHTMLとCSSを書きます。

html
<nav class="gnavi">
  <div class="gnavi__inner">
    <ul></ul>
  </div>
</nav>
css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
  outline: 1px solid #f00; // 確認用のダミースタイル
}
.gnavi ul {
  height: 100%;
  outline: 2px solid #0f0; // 確認用のダミースタイル
}

帯の高さは80pxで固定なので、.gnaviheight: 80px;を指定します。

.gnavi__innerは、サイトの横幅である980pxを指定して中央寄せとします。高さは100%で親要素と同じ高さにします。

ul要素も、高さに100%を指定して親要素と同じ高さにします。後ほどこの要素に、リンク領域をはみ出すためのスタイルを書きますが、いったん横幅は何も指定しません。

ここまでの見た目

コーディング1

li要素の追加

HTMLの方に、メニュー項目のli要素を7つ追加しましす。

html
<nav class="gnavi">
  <div class="gnavi__inner">
    <ul>
      <li><a href="#"><span>トップページ</span></a></li>      <li><a href="#"><span>サービス紹介</span></a></li>      <li><a href="#"><span>お得な<br>キャンペーン</span></a></li>      <li><a href="#"><span>料金表</span></a></li>      <li><a href="#"><span>会社概要</span></a></li>      <li><a href="#"><span>アクセス</span></a></li>      <li><a href="#"><span>お問い合わせ</span></a></li>    </ul>
  </div>
</nav>

a要素のhrefの中が空だとリンクとして認識されないので、ダミーで#を入れておきます。 グローバルナビのHTMLはこれで完成です。以降は変更ありません。

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
  outline: 1px solid #f00; // 確認用のダミースタイル
}
.gnavi ul {
  height: 100%;
  outline: 2px solid #0f0; // 確認用のダミースタイル
}

a {  color: inherit;  text-decoration: none;}

cssの方では、リンクのデフォルトのスタイル(青文字・下線)をとりあえず解除するためのスタイルを追加します。

ここまでの見た目

コーディング2

liを横ならびにする

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
  outline: 1px solid #f00; // 確認用のダミースタイル
}
.gnavi ul {
  height: 100%;
  outline: 2px solid #0f0; // 確認用のダミースタイル
  display: flex;}
.gnavi li {  flex: 1 1;}a {
  color: inherit;
  text-decoration: none;
}

ul要素にdisplay: flexを指定します。display: flexが指定された要素の子要素liはデフォルトで横ならびになります。

また、display: flexだけではli要素の横幅が伸びず左に詰まったように見えてしまうため、li要素にflex: 1 1を指定します。

これはflex-grow: 1flex-shrink: 1の省略形(ショートハンド)で、ざっくり言うと横幅が余ったら各項目で均等に割り振る横幅が足りなければ各項目の余白を均等に削減するといった感じの動きになります。

なお、ul要素にdisplay: flexを指定したことで、子要素のli要素はデフォルトで高さがいっぱいになっています。

ここまでの見た目

コーディング3

項目の間に境界線を表示する

li要素のafter擬似要素を使って、項目間の境界線を表示します。

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
  outline: 1px solid #f00; // 確認用のダミースタイル
}
.gnavi ul {
  height: 100%;
  outline: 2px solid #0f0; // 確認用のダミースタイル
  display: flex;
}
.gnavi li {
  flex: 1 1;
  position: relative;}
.gnavi li::after {  content: "";  display: block;  width: 2px;  height: 20px;  background-color: #b8b9dc;  position: absolute;  right: -1px;  top: calc((100% - 20px)/2);}.gnavi li:last-child::after {  content: none;}
a {
  color: inherit;
  text-decoration: none;
}

まず以下の部分で、幅2px、高さ20px、背景色グレーの境界線の要素を追加しています。

.gnavi li::after {
  content: "";
  display: block;
  width: 2px;
  height: 20px;
  background-color: #b8b9dc;
}

次に以下の部分で、境界線の位置をli要素の右端から1px右にずれた位置の縦位置中央に配置します。

.gnavi li {
  position: relative;
}
.gnavi li::after {
  position: absolute;
  right: -1px;
  top: calc((100% - 20px)/2);
}

topプロパティにcalcを使って少しややこしい指定をしています。これは、全体の高さから境界線の高さ20pxを引いて、残りを2で割った位置から境界線が表示されるということを実現しています。

言葉にしてもややこしいので、図にすると以下のようなイメージです。

topの計算の図解

グローバルナビの高さが80px、境界線の高さが20pxで変更がない場合は、top: 30pxと決め打ちで指定してしまっても問題ありません。

最後の項目の右側には境界線を表示しないので、以下の部分で非表示にします。

.gnavi li:last-child::after {
  content: none;
}

ここまでの見た目

コーディング4

項目の文字を中央に寄せる

この段階では、li要素の中のa要素とspan要素は、幅も高さもなく以下のような状態になっています。

※確認用のダミースタイルの位置を、li(赤)、a(緑)、span(青)に変更しています。

コーディング5

a要素は幅と高さいっぱいに、span要素は高さいっぱいにしたいので、以下のスタイルを追加します。

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
}
.gnavi ul {
  height: 100%;
  display: flex;
}
.gnavi li {
  flex: 1 1;
  position: relative;
  display: flex;  outline: 1px solid #f00; // 確認用のダミースタイル
}
.gnavi li::after {
  content: "";
  display: block;
  width: 2px;
  height: 20px;
  background-color: #b8b9dc;
  position: absolute;
  right: -1px;
  top: calc((100% - 20px)/2);
}
.gnavi li:last-child::after {
  content: none;
}
.gnavi a {
  display: flex;  flex: 1 1;  outline: 2px solid #0f0; // 確認用のダミースタイル
}
.gnavi span {
  outline: 2px solid #00f; // 確認用のダミースタイル
}

a {
  color: inherit;
  text-decoration: none;
}

display: flexを使用すると、子要素の高さがデフォルトでいっぱいになる、という性質を利用します。span要素の横幅は文字の幅の分だけで良いのでここでは何も指定しませんが、a要素は横幅いっぱいに広がって欲しいので、li要素と同じようにflex: 1 1を指定しています。

ここまでの見た目

コーディング6

文字を中央に配置する

span要素の中の文字を縦位置中央に、span要素をa要素の横位置中央に配置します。

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
}
.gnavi ul {
  height: 100%;
  display: flex;
}
.gnavi li {
  flex: 1 1;
  position: relative;
  display: flex;
  outline: 1px solid #f00; // 確認用のダミースタイル
}
.gnavi li::after {
  content: "";
  display: block;
  width: 2px;
  height: 20px;
  background-color: #b8b9dc;
  position: absolute;
  right: -1px;
  top: calc((100% - 20px)/2);
}
.gnavi li:last-child::after {
  content: none;
}
.gnavi a {
  display: flex;
  flex: 1 1;
  outline: 2px solid #0f0; // 確認用のダミースタイル
  justify-content: center;  padding: 0 20px;}
.gnavi span {
  outline: 2px solid #00f; // 確認用のダミースタイル
  display: flex;  align-items: center;}

a {
  color: inherit;
  text-decoration: none;
}

a要素には、子要素spanを横位置中央に配置するためのjustify-content: centerと、項目の境界線との間に最低限空ける余白の20pxをpaddingで指定します。

また、span要素の中のテキストを縦位置中央にするため、spanにもdisplay: flexalign-items: center;を指定します。

ここまでの見た目

コーディング7

最初と最後の項目のリンク領域をはみ出す

ul要素の左右のマージンに負の値を設定し、親のdiv要素からはみ出すようにします。

※確認用のダミースタイルの位置を、div(赤)、li(緑)に変更しています。

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
  outline: 2px solid #f00; // 確認用のダミースタイル
}
.gnavi ul {
  height: 100%;
  display: flex;
  margin: 0 -20px;}
.gnavi li {
  flex: 1 1;
  position: relative;
  display: flex;
  outline: 1px solid #0f0; // 確認用のダミースタイル
}
.gnavi li::after {
  content: "";
  display: block;
  width: 2px;
  height: 20px;
  background-color: #b8b9dc;
  position: absolute;
  right: -1px;
  top: calc((100% - 20px)/2);
}
.gnavi li:last-child::after {
  content: none;
}
.gnavi a {
  display: flex;
  flex: 1 1;
  justify-content: center;
  padding: 0 20px;
}
.gnavi span {
  display: flex;
  align-items: center;
  word-break: keep-all;}

a {
  color: inherit;
  text-decoration: none;
}

ul要素の左右のmargin-20pxを指定しています。この-20pxという値は、文字と境界線との間に最低限設けた余白の20pxと同じ値です。

また、span要素にword-break: keep-allを追加し、文字数が多い場合でも文字が自動で折り返さないようにしています。こうすることで、文字数に応じて横幅の長さが可変になりました。

ここまでの見た目

コーディング8

通常時とホバー時の見た目を追加

文字に、通常時とホバー時の見た目のスタイルを追加します。確認用のダミースタイルはすべて削除します。

css
.gnavi {
  height: 80px;
  background-color: #eaf3ff;
  box-shadow: 0 3px 6px -3px #000000 inset;
  font-size: 1.2rem;  font-weight: bold;}
.gnavi__inner {
  width: 980px;
  height: 100%;
  margin: 0 auto;
}
.gnavi ul {
  height: 100%;
  display: flex;
  margin: 0 -20px;
}
.gnavi li {
  flex: 1 1;
  position: relative;
  display: flex;
}
.gnavi li::after {
  content: "";
  display: block;
  width: 2px;
  height: 20px;
  background-color: #b8b9dc;
  position: absolute;
  right: -1px;
  top: calc((100% - 20px)/2);
}
.gnavi li:last-child::after {
  content: none;
}
.gnavi a {
  display: flex;
  flex: 1 1;
  justify-content: center;
  padding: 0 20px;
}
.gnavi a:hover span {  color: #ff7600;  position: relative;}.gnavi a:hover span::after {  content: "";  position: absolute;  display: block;  height: 4px;  width: 100%;  bottom: 0;  background-color: #ff7600;  border-radius: 5px;}.gnavi span {
  display: flex;
  align-items: center;
  word-break: keep-all;
}

a {
  color: inherit;
  text-decoration: none;
}

ここまでの見た目

コーディング9

上下のパーツを追加

最後に、グローバルナビの上のヘッダーと、下のメインビジュアルを追加します。

これらのコードは本筋から逸れるため記載しませんが、すべてのコードを見たい方はCodePenをご覧ください。

コーディング10

完成形をCodePenで見る

なお、CodePenではHTMLのプリプロセッサーにPug、CSSのプリプロセッサーにSCSSを使用しています。PugやSCSSの記法に馴染みのない方は、PugHtmlSassMeisterなどのサービスにコードをコピペしてHTMLやCSSをご確認頂ければと思います。

うまくできていないところ。。。

これでコードは完成ですが、実は、項目の数や文字数、デザインによって、調整しなければならないところが残っています。

最初か最後の項目の文字数が少ない場合

最初もしくは最後の項目の文字数が他の項目より少なく、文字と境界線との間の余白が20pxより多く空いている場合は、文字の端が揃いません。

失敗例1

この場合、ul要素に指定しているネガティブマージンを、margin-right: -35pxなどと、左右で個別の値を指定する必要があります。

すべての文字が少なくて(小さくて)余白が余っている場合

また文字数が少ないとか、フォントサイズが小さいなどといった理由で、すべての項目と境界線の間が20px以上空いている場合も、文字の端が揃いません。

失敗例2

こういった場合も、ul要素に指定しているネガティブマージンを、margin: 0 -28pxなどと個別に指定する必要があります。

これらはHTMLとCSSだけでは解決できす、コンテンツに応じて調整が必要になる箇所が残ってしまいました。良い解決策がないか、折に触れて考えていきたいと思います。

終わりに

以上です。比較的応用のきくHTMLとCSSになっているのではないかと思っています。よろしければご参考にどうぞ。

最後までお読みいただきありがとうございました。