はじめに
グローバルナビに以下のようなデザインが指定されているウェブサイトのHTMLとCSSのコーディングを行なっていました。(デザインは仮のものです)
よく見る形だし、さくっと組めるかなと着手前は考えたのですが、やってみたら予想よりも考えなきゃいけないことが多くありました。CSSの良い復習にもなったので、自分へのメモも兼ねて、コーディングの流れをじっくり解説します。
なお、フレックスボックス(display: flex
)を多用したコーディングを解説しますので、対応ブラウザにはご注意ください。
デザイン仕様の確認
コーディングを始める前に、デザインカンプからコーディングに必要な情報を抜き出して整理します。
項目の横幅
まず、項目ひとつのデザインを確認します。
項目の横幅は、文字数によって可変のようです。そして、もっとも文字数の多い6文字の項目でも、文字と左右の境界線との間には、最低20pxの余白があります。文字数が少ない場合、この余白が20pxよりも広くなっています。
またデザインカンプでは表現されていませんが、操作のしやすさを考えて、リンク領域の幅は境界線から境界線まで、高さは帯の高さいっぱいまでとします。
マウスホバーした際に下線が表示されるデザインですが、この下線はリンク領域いっぱいでなく、文字数の横幅の分だけの長さを持つ点に注意します。
項目と項目の間の境界線は、項目が2行の場合でも高さは変化せず、常に帯の中心の高さにいます。最初の項目の前と、最後の項目の後には境界線は表示しません。
グローバルナビ全体の横幅について考えます。
上にあるヘッダーや、下にあるメインビジュアルは幅980pxで作られており、グローバルナビも端を揃えてデザインされています。しかし、グローバルナビの項目の文字の端がそろっているため、実際には上の図のように、リンク領域がはみ出るように組んだ方が良さそうだと考えました。
最初と最後の項目のクリック領域を端に合わせて削ってしまうよりは、他の項目と同じように文字の左右にもクリック可能な余白があることで、操作感が良くなると思います。デザインカンプに表現されていない部分ではあるため、デザイナーにすぐ確認できる環境であれば確認した方が良いでしょう。
HTML設計
デザイン仕様が確認できたので、次にHTML構造を考えていきます。
まずいちばん外側のHTMLから、以下のような構造を作ります。
nav (帯の背景色や装飾)
┗ div (横幅980pxを定める)
┗ ul
内側のul
要素が、親のdiv
要素を突き抜けています。これはデザイン仕様の、リンク領域を削ることなく文字の端を揃えるためのもので、詳細は後ほど解説します。
ul
以下の構造は以下のようにします。
ul
┗ li
┃┗ a
┃ ┗ span(ホバー時に文字の長さの分だけ下線を引くために使う)
┗ li
┃┗ a
┃ ┗ span
(以下、項目の数だけ繰り返し)
コーディング
それでは実際にHTMLとCSSをコーディングします。
この記事ではできるだけレイアウトに限った話だけにしたいので、Reset.cssを読み込んでブラウザのデフォルトスタイルを無効にしています。
nav〜ulのコーディング
まず、一番外側からul
要素までのHTMLとCSSを書きます。
<nav class="gnavi">
<div class="gnavi__inner">
<ul></ul>
</div>
</nav>
.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で固定なので、.gnavi
にheight: 80px;
を指定します。
.gnavi__inner
は、サイトの横幅である980pxを指定して中央寄せとします。高さは100%で親要素と同じ高さにします。
ul
要素も、高さに100%を指定して親要素と同じ高さにします。後ほどこの要素に、リンク領域をはみ出すためのスタイルを書きますが、いったん横幅は何も指定しません。
ここまでの見た目
li要素の追加
HTMLの方に、メニュー項目のli
要素を7つ追加しましす。
<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はこれで完成です。以降は変更ありません。
.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の方では、リンクのデフォルトのスタイル(青文字・下線)をとりあえず解除するためのスタイルを追加します。
ここまでの見た目
liを横ならびにする
.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: 1
とflex-shrink: 1
の省略形(ショートハンド)で、ざっくり言うと横幅が余ったら各項目で均等に割り振る、横幅が足りなければ各項目の余白を均等に削減するといった感じの動きになります。
なお、ul
要素にdisplay: flex
を指定したことで、子要素のli
要素はデフォルトで高さがいっぱいになっています。
ここまでの見た目
項目の間に境界線を表示する
li
要素のafter
擬似要素を使って、項目間の境界線を表示します。
.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で割った位置から境界線が表示されるということを実現しています。
言葉にしてもややこしいので、図にすると以下のようなイメージです。
グローバルナビの高さが80px、境界線の高さが20pxで変更がない場合は、top: 30px
と決め打ちで指定してしまっても問題ありません。
最後の項目の右側には境界線を表示しないので、以下の部分で非表示にします。
.gnavi li:last-child::after {
content: none;
}
ここまでの見た目
項目の文字を中央に寄せる
この段階では、li
要素の中のa
要素とspan
要素は、幅も高さもなく以下のような状態になっています。
※確認用のダミースタイルの位置を、li
(赤)、a
(緑)、span
(青)に変更しています。
a
要素は幅と高さいっぱいに、span
要素は高さいっぱいにしたいので、以下のスタイルを追加します。
.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
を指定しています。
ここまでの見た目
文字を中央に配置する
span
要素の中の文字を縦位置中央に、span
要素をa
要素の横位置中央に配置します。
.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: flex
とalign-items: center;
を指定します。
ここまでの見た目
最初と最後の項目のリンク領域をはみ出す
ul
要素の左右のマージンに負の値を設定し、親のdiv
要素からはみ出すようにします。
※確認用のダミースタイルの位置を、div
(赤)、li
(緑)に変更しています。
.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
を追加し、文字数が多い場合でも文字が自動で折り返さないようにしています。こうすることで、文字数に応じて横幅の長さが可変になりました。
ここまでの見た目
通常時とホバー時の見た目を追加
文字に、通常時とホバー時の見た目のスタイルを追加します。確認用のダミースタイルはすべて削除します。
.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;
}
ここまでの見た目
上下のパーツを追加
最後に、グローバルナビの上のヘッダーと、下のメインビジュアルを追加します。
これらのコードは本筋から逸れるため記載しませんが、すべてのコードを見たい方はCodePenをご覧ください。
なお、CodePenではHTMLのプリプロセッサーにPug、CSSのプリプロセッサーにSCSSを使用しています。PugやSCSSの記法に馴染みのない方は、PugHtmlやSassMeisterなどのサービスにコードをコピペしてHTMLやCSSをご確認頂ければと思います。
うまくできていないところ。。。
これでコードは完成ですが、実は、項目の数や文字数、デザインによって、調整しなければならないところが残っています。
最初か最後の項目の文字数が少ない場合
最初もしくは最後の項目の文字数が他の項目より少なく、文字と境界線との間の余白が20pxより多く空いている場合は、文字の端が揃いません。
この場合、ul
要素に指定しているネガティブマージンを、margin-right: -35px
などと、左右で個別の値を指定する必要があります。
すべての文字が少なくて(小さくて)余白が余っている場合
また文字数が少ないとか、フォントサイズが小さいなどといった理由で、すべての項目と境界線の間が20px以上空いている場合も、文字の端が揃いません。
こういった場合も、ul
要素に指定しているネガティブマージンを、margin: 0 -28px
などと個別に指定する必要があります。
これらはHTMLとCSSだけでは解決できす、コンテンツに応じて調整が必要になる箇所が残ってしまいました。良い解決策がないか、折に触れて考えていきたいと思います。
終わりに
以上です。比較的応用のきくHTMLとCSSになっているのではないかと思っています。よろしければご参考にどうぞ。
最後までお読みいただきありがとうございました。