Lopan.jp

動くCSSのためのメモ。
タブコンテンツ

タブボタンをクリックすることで、表示する内容を切り替えるタブコンテンツも、CSSだけでできちゃいます。コンテンツの切り替わり方のバリエーションや、タブボタンで表示レイアウトを切り替えるみたいな使い方など、CSSで作るタブコンテンツについてのまとめです:)

CSSで切り替える仕組み

CSSで切り替えを行うためには、ちょっとした下ごしらえが必要なので、まずはその仕組みについて、下の仕組みがよくわかるサンプルのソースコードを見ながら要点を解説です。

HTML

まずはタブボタンと、それに付随するコンテンツを設置します。
タブボタンに<label>を使うのがポイントです:)

<ul class="tabBtn">
	<li><label for="tab-1">タブ1</label></li>
	<li><label for="tab-2">タブ2</label></li>
	<li><label for="tab-3">タブ3</label></li>
	<li><label for="tab-4">タブ4</label></li>
	<li><label for="tab-5">タブ5</label></li>
</ul>
<div class="tabContents">
	<section>タブコンテンツ1</section>
	<section>タブコンテンツ2</section>
	<section>タブコンテンツ3</section>
	<section>タブコンテンツ4</section>
	<section>タブコンテンツ5</section>
</div>

次に、コンテンツを切り替えるスイッチの役割となる<input type="radio">(ラジオボタン)を5つ、上記ソースコードの手前に設置します。

<input type="radio" name="switch" id="tab-1" checked>
<input type="radio" name="switch" id="tab-2">
<input type="radio" name="switch" id="tab-3">
<input type="radio" name="switch" id="tab-4">
<input type="radio" name="switch" id="tab-5">

name属性の値を統一して、ラジオボタンをひとつのグループに(ここではswitchに)します。それから<input>id属性の値を、<label>for属性の値と同じにして、タブボタンとラジオボタンを関連付けます。
ひとつめの<input>にはchecked属性を付けて、初期状態ではタブコンテンツ1が開いているようにしておきます。
以上で下ごしらえ完了!

CSS

そして、以降の指定が、コンテンツの表示/非表示を切り替えるための仕組みの部分になります。
まずは、タブボタンを切り替えた時の、ボタンのスタイルを指定します。

#tab-1:checked ~ .tabBtn label[for="tab-1"],
#tab-2:checked ~ .tabBtn label[for="tab-2"],
#tab-3:checked ~ .tabBtn label[for="tab-3"],
#tab-4:checked ~ .tabBtn label[for="tab-4"],
#tab-5:checked ~ .tabBtn label[for="tab-5"] {
	background: indianred;
}

1行めを分解して見てみます。
#tab-1:checkedは「id属性がtab-1の要素がチェックされている状態」を選択します。
~チルダは、一般兄弟結合子(間接結合子)といって、セレクタの間にこの記号を挟むと、「左側のセレクタより後に書かれた兄弟要素(同じ階層にある要素)である右側のセレクタ」を選択することになります。
最後のlabel[for="tab-1"]は、「for属性がtab-1のlabel要素」を選択します。

つまり、「チェックされている#tab-1要素より後に書かれた、同じ階層にある.tabBtn要素の中の、for属性がtab-1のlabel要素」にbackground: indianredを指定するって事になります。
要するに「1つめのラジオボタンがチェックされてる時は、1つめのタブボタンを赤くする」ということ;)

1つめのタジオボタンがcheckedの時は、1つめのラブボタンの色が変わる

タブボタンを切り替えた時のコンテンツのスタイルもタブボタンと同じ要領で、「チェックされている#tab-1要素より後に書かれた、同じ階層にある.tabContents要素の中の、ひとつめのsection要素」というように、タブコンテンツを特定します。

.tabContents section {
	opacity: .1;
}
#tab-1:checked ~ .tabContents section:nth-child(1),
#tab-2:checked ~ .tabContents section:nth-child(2),
#tab-3:checked ~ .tabContents section:nth-child(3),
#tab-4:checked ~ .tabContents section:nth-child(4),
#tab-5:checked ~ .tabContents section:nth-child(5) {
	opacity: 1;
	background: white;
}

チェックされていない時は透明(ここでは仕組みが見えるように半透明)にしておき、チェックされたボタンを不透明にすることで、表示/非表示が切り替わるって寸法です。

ページトップへ戻る

:targetを使ってタブコンテンツ

上の「仕組みがよくわかるサンプル」では、ラジオボタンを使ってコンテンツの表示/非表示を切り替えてましたが、アンカーリンクを使っても、同じように切り替える事ができます。

例えばURLの末尾に「#contents-1」と付けてリンクすると、ページ内の<div id="contents-1">の位置へリンクする事ができますよね。id属性を持った要素はすべてアンカーポイントとなって、その要素の場所へリンクできるようになります。
そんなアンカーリンクされている要素に、スタイルを適用する事ができるのが:targetという疑似クラス。
下記のように指定すれば、URLの末尾が「#contents-1」としてアクセスした時に、contents-1というid属性を持った要素を赤いボーダーで囲みます。

#contents-1:target {
	border: 2px solid indianred;
}

URLを利用してスタイルを適用しているので、アンカーリンクする度に特定の要素のスタイルを切り替えることが可能です。しかもURLが切り替わるから、ブラウザの戻るボタンで履歴を辿る事もできちゃいます。タブコンテンツの内容が濃い場合にはこちらの方法が持ってこいかもしれませんねー:D

URL末尾の#ハッシュの値によって、タブボタンの色が変わる

先ほどの、仕組みがよくわかるサンプル:checkedで切り替えてた部分を、:targetで切り替わるように修正してみましょう。

HTML

<input type="radio">だったところを<div>に置き換えます。あくまでスイッチの役割を担う空っぽdiv要素になります。id属性はそのままに、name属性の代わりにclass属性でswitchという値を付与しました。

input要素をdiv要素に変える
<input type="radio" name="switch" id="tab-1" checked>
	⇓
<div id="tab-1" class="switch"></div>

タブボタンは<label>から<a>に置き換えて、for属性でinput要素と紐付けてたところを、今度はhref属性にして、前述の<div>へそれぞれリンクするようにします。

label要素をa要素に変える
<li><label for="tab-1">タブ1</label></li>
	⇓
<li><a href="#tab-1">タブ1</a></li>

CSS

アンカーをクリックすると、ウィンドウの位置がその要素の位置まで移動しちゃいますが、その要素にdisplay: noneを指定すれば移動しなくなります。

ウィンドウ位置を移動させないための指定
.switch {
	display: none;
}
あとは、HTMLに倣ってlabelだったところをaに、forだったところをhrefに、:checkedだったところを:targetに置き換えれば完了ー!

タブボタン
.tabBtn li label { ... }
	⇓
.tabBtn li a { ... }
.tabBtn li label:hover { ... }
	⇓
.tabBtn li a:hover { ... }
#tab-1:checked ~ .tabBtn li [for="tab-1"] { ... }
	⇓
#tab-1:target ~ .tabBtn li [href="#tab-1"] { ... }
タブコンテンツ
#tab-1:checked ~ .tabContents section:nth-child(1) { ... }
	⇓
#tab-1:target ~ .tabContents section:nth-child(1) { ... }

タブを切り替えた後でブラウザの戻るボタンを押してみてください。URLを移動することでタブの表示を切り替えてるので、タブ切り替えが履歴として残るため、切り替える前のタブに戻ることができるんですね;)

ページトップへ戻る

アニメーションして切り替わる

タブコンテンツにコンテンツを入れてみました。

タブコンテンツも、マウスオーバーエフェクトの時みたく、transitionを使って切り替わり方をアレンジすれば、印象的な表現ができちゃいます:)

スライドして切り替わる

タブボタンの切り替えに合わせて、<div class="tabContents">の位置を上下にズラせば、スクロールコンテンツみたいなものもお手のもの。

タブボタンに対応するタブコンテンツの位置

上図のように、あらかじめtransform: translateY()で、タブボタンに対応するコンテンツの位置を指定しておきます。例えば下記なら、「タブ2」がチェックされてる時には、.tabContentsを上へ20%ズラす、ということ。.tabContents全体の高さを100%とした20%分。

#tab-2:checked ~ .tabContents {
	transform: translateY(-20%);
}

これをタブコンテンツ分(このサンプルでは5つ)用意し、transitionを適用すれば、上下にスライドしながらコンテンツが切り替わるようになります。

あれれ…、タブコンテンツがタブボタンの上に被さってタブボタンが押せなくなっちゃう…X(。。

タブボタンをタブコンテンツより手前の階層に

こんな時は.tabBtnz-indexを指定して、手前に配置すれば安心。

.tabBtn {
	position: relative;
	z-index: 1;
}

z-indexpositionプロパティstatic以外の値が指定してある要素でないと効果がないので、position: relativeも一緒に指定。

無事タブボタンが押せるようになりました:D

横にスライドして切り替わる

タブコンテンツを横並びにして、transform: translateX()で横へズラせば、横スライドもお手のもの。

.tabContentsの横幅20%分ずつスライドする

.tabContentsの横幅をウィンドウ幅5つ分(500%)にして、その中にタブコンテンツを横並びにします。そのままだとスクロールバーが出てしまうので、全体を包む要素(ここでは<body>overflow: hiddenを指定して、スクロールバーを出さないようにすればできあがり。

ページトップへ戻る

表示モード切り替え

ここまでは、いくつかのコンテンツをタブボタンで切り替えてましたが、タブボタンのON/OFFで、同一コンテンツのスタイルを変えるようにすれば、表示モード切り替えみたいな事もできます。

ここでは、「画像とテキスト全部入りのレイアウト」、「画像が主役のレイアウト」、「テキストのみのレイアウト」の3タイプの表示モードを、タブボタンで切り替えてみます。

下記のような記述で、各モード専用のスタイルを用意しておけば、チェックされているタブボタンに合わせて、指定したスタイルが適用される仕組みです。

#normalList:checked ~ .menu elements {
	/* 全部入り用のスタイル */
}
#imageList:checked ~ .menu elements {
	/* 画像が主役用のスタイル */
}
#textList:checked ~ .menu elements {
	/* テキストのみ用のスタイル */
}

アニメーションを加える

上のサンプルだと、transitionを指定してる所だと、前のモードのスタイルがちょっと見えちゃったり、モードの切り替わり方がちょっとかっこわるいですよね…。
下記のようにアニメーションを定義して、タブボタンが:checkedになった時に一度だけ再生するようにしてみます。
@keyframesで、ちょっと間を置いてからフェードインするアニメーションを用意してみました。

@keyframes fadeIn {
	0% { opacity: 0; }
	30% { opacity: 0; }
	100% { opacity: 1; }
}

このアニメーションを、チェックされた時のコンテンツに適用します。

input:checked ~ .menu {
	animation: fadeIn 1s;
}

ページが開いた時に一度だけフェードインしますが、タブボタンで切り替える時には、アニメーションが再生されませんね…:(:checkedされるタブが切り替わっても「fadeIn」というアニメーションはすでに再生された後なので、それ以上は再生されないみたいです…。
そこで、下のサンプルでは、同じアニメーションを各タブ用に3つ用意して、各タブごとに違うアニメーションを指定してみます。
あと、最初にちょっと間を置くのは、animation-delayプロパティで調整することにして、アニメーション自体はフェードインするだけのものに修正します。

#normalList:checked ~ .menu {
	animation: fadeIn 1s .3s both;
}
#imageList:checked ~ .menu {
	animation: fadeIn2 1s .3s both;
}
#textList:checked ~ .menu {
	animation: fadeIn3 1s .3s both;
}
@keyframes fadeIn {
	from { opacity: 0; }
	to { opacity: 1; }
}
@keyframes fadeIn2 {
	from { opacity: 0; }
	to { opacity: 1; }
}
@keyframes fadeIn3 {
	from { opacity: 0; }
	to { opacity: 1; }
}

:checkedされるタブが変わる度に再生するアニメーションも切り替えることで、タブを切り替える度にアニメーションが再生されるようになりましたね:D

Comment & Pingback

4 Comments! for タブコンテンツ

  1. さやか

    理想的なタブデザインを紹介していただきありがとうございます。モバイルでは横スライド、PCでは縦スライドのようにCSSを分けたいのですが上手くいきません。メディアクエリの順番的に先にモバイル用の横スライドのCSSを書いて続けてPC用の縦スライドのCSSを置きたいのですがどうにも上手くいかずに困っています。PCの場合左横にサイドバーと兼用する形でヘッダーを固定しているので、横スライドだとその上をコンテンツが通過してしまうのです。

    Reply
    • _watercolor

      > さやかさん

      コメントどうもありがとうございます!
      タブコンテンツ活用していただけてうれしいです:)

      さて、モバイルでは横スライドで、PCでは縦スライドにするには、おっしゃる通り、メディアクエリでスタイルを書き分ける必要があります。
      下サンプルに、アニメーションして切り替わるのところで紹介しているサンプルを元に、「モバイルでは横スライドで、PCでは縦スライドする」ようにしてみました;)


      縦スライド用のスタイルと、横スライド用のスタイルの違う点は以下の2箇所。

      • コンテンツを「横並びにするための指定display: flexwidth: 500%flex-basis: 20%」のあるなし
      • コンテンツをスライドする方向translateXtranslateYの違い

      ポイントは、縦スライドするPC用のスタイルに「横並びにするための指定」が適用されないように、下記のようにメディアクエリをしっかり分けて書くようにするとよいです:)

      ※ここでは、767px以下max-width: 767pxをモバイル、768px以上min-width: 768pxをPCということにしています。

      1. /* モバイル用(横スライド) */
      2. @media screen and (max-width: 767px) {
      3. .tabContents {
      4. display: flex;
      5. width: 500%;
      6. }
      7. .tabContents section {
      8. flex-basis: 20%;
      9. }
      10. #tab-1:checked ~ .tabContents {
      11. transform: translateX(0);
      12. }
      13. }
      14. /* PC 用(縦スライド) */
      15. @media screen and (min-width: 768px) {
      16. #tab-1:checked ~ .tabContents {
      17. transform: translateY(0);
      18. }
      19. }

      詳しくは、上サンプルのソースコードをご参照くださいませ!X)


      それから、

      PCの場合左横にサイドバーと兼用する形でヘッダーを固定しているので、横スライドだとその上をコンテンツが通過してしまうのです

      とのことですが、下サンプルのような感じでしょうか…?


      たしかに、タブコンテンツからハミ出したコンテンツが、ヘッダーに被ってしまってますね:(
      これを解決する方法は以下の2つ。

      • ヘッダーのz-indexをタブコンテンツよりも上になるようにする
      • タブコンテンツを<div>で括って、overflow: hiddenを指定する

      下サンプルは、ヘッダーにz-indexを指定することで、ヘッダーをタブコンテンツよりも上に重なるようにしています。


      下サンプルでは、タブコンテンツをまるごと<div class="tab"></div>で括って、.taboverflow: hiddenを指定することで、タブコンテンツからハミ出した要素は非表示になるようにしています。


      どうだろうか、こちらで解決できるとよいですが…。
      てなもんで、今後とも、Lopan.jpをどうぞよろしくです。

      Reply
  2. さやか

    詳しい解説ありがとうございます。質問した点については無事に解決しました。
    ただタブコンテンツ内の高さにかなり差があり、高さのないタブを選択した状態でできる下部の空白が気になってしまいました。これをコンテンツそれぞれの高さに合わせて上手い具合に伸縮させる方法はないでしょうか?重ね重ね申し訳ありません。

    Reply
    • _watercolor

      > さやかさん
      コメントありがとうございます、無事、解決したようでなによりです:D

      たしかに、タブコンテンツごとの高さが違うと、そうなってしまいますねぇ…(下サンプル)


      これは、.tabContents要素に指定しているdisplay: flexによって、.tabContents要素の高さが、一番高いタブコンテンツに準拠しているからです。

      タブコンテンツがすべて、一番高い要素(ここではタブ5)と同じ高さになってる

      いちばん簡単な方法は、選択されているタブコンテンツが一番高いタブコンテンツになるようにするために、選択されていないタブコンテンツにはheight: 0を指定しておいて、選択されているタブコンテンツにheight: autoを指定する方法。

      1. .tabContents section {
      2. height: 0;
      3. opacity: .1;
      4. }
      5. #tab-1:checked ~ .tabContents #section-1,
      6. #tab-5:checked ~ .tabContents #section-5 {
      7. height: auto;
      8. opacity: 1;
      9. background: white;
      10. }

      選択していない要素の高さを0pxにしてるから、選択している要素が一番高い要素になる

      けれど、せっかくタブコンテンツをアニメーションしているのに、タブコンテンツの下のコンテンツがガタッとなってしまうのは、ちょっとカッコ悪いですね…:(
      やっぱり、CSSだけで作るタイプのタブコンテンツは、高さを固定して、各タブの中でスクロールできるようにするのが、一番スマートいさぎよいかもしれません。

      下サンプルでは、画像の高さが最大でも200pxまでになるようにしていて、その下に8pxのマージンを指定しているので、タブコンテンツの高さはそれに合わせて208pxに固定して、それよりもハミ出す場合はスクロールバーが出るように、overflow: autoを指定しています(下図参照)

      “最低でも画像は入りきる高さ”を基準に高さを固定することにする
      1. .tabContents section {
      2. overflow: auto;
      3. height: 208px;
      4. padding: 20px;
      5. background: white;
      6. }

      CSSではそこまでこだわれないのですけれど、なにか良い方法を思い付いたら記事にしてみます!おたのしみにぃX9
      ではでは、引き続きLopan.jpをどうぞよろしくです!

      Reply

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください