CSSだけでスライドするタブ切り替えを考えてみた

CSSだけでスライドするタブ切り替え

汎用性の高いタブ切り替えを元にスライドして切り替わる動きをCSSだけで出来ないかなぁって事で考えてみました。

ちゃんと左右にスクロールしたようにスライドします。

CSSだけでスライドするタブ切り替えのキャプチャ
こんな感じ
スポンサーリンク

サンプル

See the Pen CSSだけでスライドするタブ切り替え by 草村 (@kusamura_mono) on CodePen.38847

基本構造

<div class="tab-wrap">
    <input id="TAB-01" type="radio" name="TAB" class="tab-switch" checked="checked" /><label class="tab-label" for="TAB-01">ボタン1</label>
    <div class="tab-content">
        <p>コンテンツ1</p>
    </div>
    <input id="TAB-02" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-02">ボタン2</label>
    <div class="tab-content">
        <p>コンテンツ2</p>
    </div>
    <input id="TAB-03" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-03">ボタン3</label>
    <div class="tab-content">
        <p>コンテンツ3</p>
    </div>
</div>

HTMLはこんな感じの構造です。実際の動作はサンプル(CodePen)で確認出来ます。

DIV.tab-wrap >
  INPUT[type="radio"].tab-switch + LABEL.tab-label + DIV.tab-content +
  INPUT[type="radio"].tab-switch + LABEL.tab-label + DIV.tab-content +
  INPUT[type="radio"].tab-switch + LABEL.tab-label + DIV.tab-content

ラジオボックスで表示が切り替わる仕組みです。
ボタン部分(.tab-label)はorder: -1;で並べています。

このタブ切り替えは以前記事にした汎用性の高いタブ切り替えのHTMLを使用しています。

基本的なタブ切り替えの構造についてはこちらの記事でざっくり解説していますのでよろしければご覧ください。

CSS解説

.tab-wrap {
	background: White;
	box-shadow: 0 0 5px rgba(0,0,0,.1);
	display: flex;
	flex-wrap: wrap;
	overflow: hidden;
	padding: 0 0 20px;
}

.tab-label {
	color: Gray;
	cursor: pointer;
	flex: 1;
	font-weight: bold;
	order: -1;
	padding: 12px 24px;
	position: relative;
	text-align: center;
	transition: cubic-bezier(0.4, 0, 0.2, 1) .2s;
	user-select: none;
	white-space: nowrap;
	-webkit-tap-highlight-color: transparent;
}

.tab-label:hover {
	background: rgba(0, 191, 255,.1);
}

.tab-switch:checked + .tab-label {
	color: DeepSkyBlue;
}

.tab-label::after {
	background: DeepSkyBlue;
	bottom: 0;
	content: '';
	display: block;
	height: 3px;
	left: 0;
	opacity: 0;
	pointer-events: none;
	position: absolute;
	transform: translateX(100%);
	transition: cubic-bezier(0.4, 0, 0.2, 1) .2s 80ms;
	width: 100%;
	z-index: 1;
}

.tab-switch:checked ~ .tab-label::after {
	transform: translateX(-100%);
}

.tab-switch:checked + .tab-label::after {
	opacity: 1;
	transform: translateX(0);
}

.tab-content {
	height:0;
	opacity:0;
	padding: 0 20px;
	pointer-events:none;
	transform: translateX(-30%);
	transition: transform .3s 80ms, opacity .3s 80ms;
	width: 100%;
}

.tab-switch:checked ~ .tab-content {
	transform: translateX(30%);
}

.tab-switch:checked + .tab-label + .tab-content {
	height: auto;
	opacity: 1;
	order: 1;
	pointer-events:auto;
	transform: translateX(0);
}

.tab-wrap::after {
	content: '';
	height: 20px;
	order: -1;
	width: 100%;
}

.tab-switch {
	display: none;
}

ここではスライドするように見えるCSSの指定について解説していきます。

  1. アクティブなタブよりのタブ
  2. アクティブなタブよりのタブ
  3. アクティブなタブ
タブ切り替えのボタン

にはそれぞれ以下のコードでスタイルを適用出来ます。

ボタン部分
/* 1.ボタン・アクティブなタブより前のボタン */
.tab-label {}

/* 2.アクティブなタブより後のボタン(アクティブなタブ含む) */
.tab-switch:checked ~ .tab-label {}

/* 3.アクティブなタブのボタン */
.tab-switch:checked + .tab-label {}
コンテンツ部分
/* 1.コンテンツ・アクティブなタブより前のコンテンツ */
.tab-content {}

/* 2.アクティブなタブより後のコンテンツ(アクティブなタブ含む) */
.tab-switch:checked ~ .tab-content {}

/* 3.アクティブなタブのコンテンツ */
.tab-switch:checked + .tab-label + .tab-content {}

左右に動く仕組み

アクティブタブよりのコンテンツにはtransformの値にtranslateX(-30%)(左寄り)、
にはtranslateX(30%)(右寄り)を指定する事でアクティブに切り替えた時の左右の流れを表現します。

アクティブタブ以外を左右にズラす
解説用に色分けしています

完全に左右に並んでスクロールしているように見せたい場合は-30%/30%-100%/100%にしてopacity:0;を削除でいい感じになるかと思います。

アンダーバーの切り替わりも同じ要領で左右に寄せます。
寄せる方向は前述のコンテンツとは左右逆にします。

アクティブタブよりのアンダーバーにはtranslateX(100%) (右寄り)、にはtranslateX(-100%)(左寄り)を指定する事でアクティブに切り替えた時の左右の流れを表現出来ます。

※各ボタンのアンダーバーは疑似要素.tab-label::afterで表現しています。

スライドするタブ切り替えの左右の動き
左右の動き

そして非アクティブタブのコンテンツにはheight:0;を指定し、且つoverflowvisible(初期値/指定なし)とします。

hidden(はみ出たコンテンツを隠す)を指定するとコンテンツが即座に隠れてしまい左右にスライドする表現が見えなくなるので注意。

非アクティブコンテンツは高さを0に
背景が無くなり高さが0になったのがわかる

さらに、アクティブタブのコンテンツorder:1;と指定。
これでアクティブコンテンツが最後尾になるためすべてのコンテンツが重なり縦軸が揃います。

アクティブコンテンツにorderを指定
アクティブ(コンテンツ2)が最後尾に

画像だけでは少し分かりづらいですが、orderを指定した事でコンテンツの並び順が変更されています。

こんな並び順
  • コンテンツ1 order:0非アクティブ高さ0)
  • コンテンツ3 order:0非アクティブ高さ0)
  • コンテンツ2 order:1アクティブ高さauto)
orderを指定しないと…
タブ切り替えが上手くいかない例
例) 3→2に切り替えた時コンテンツ3が下にズレている

フェードイン/フェードアウトはopacity(透明度)で表現しており、非アクティブには0(透明)、アクティブには1(不透明)を指定します。

非アクティブコンテンツ透明なだけなので、コンテンツの中身(リンク等)の誤作動がないようpointer-events:none;を指定。
アクティブタブautoでこれを相殺してください。
※ボタンのアンダーバー.tab-label::afterにもnone指定。

.tab-wrap::afterはボタンとコンテンツ間の余白調整用です。

ボタンとコンテンツの余白
ボタンとコンテンツの余白調整

タブの数は増減可能

  • CSSの追加は必要ありません。
  • タブ幅は勝手に横幅いっぱいに伸縮します。> flex:1;
  • タブが多くて幅が収まらない場合ボタンは折り返して2行になります。

出来ない事など

  • ボタンを折り返さず1行で横スクロールに…というのは構造的に厳しい。※1
  • それぞれのボタンの幅に著しく差がある場合アンダーバーがちょっと荒ぶるかも。
  • 高さの変化をtransitionで表現出来ない(height:0;で重ねているため)※2

▼※1 ちょっとHTMLを足してあれこれすると出来なくはないけど柔軟性が低いので実用的ではない(他の方法は今の所思いつかない)

See the Pen CSSだけでスライドするタブ切り替え(ボタン横スクロール) by 草村 (@kusamura_mono) on CodePen.38847

▼※2 コンテンツ内の「幅の固定」+「高さの上限」がある程度決まっていれば一応なんとなくぬるっと動くように出来ます。あくまでなんとなくです。

See the Pen CSSだけでスライドするタブ切り替え2 by 草村 (@kusamura_mono) on CodePen.38847

実用性

今のところ自分が確認可能な主要ブラウザでは動作していますが、まだ実際に運用していないため思いもよらぬ不具合はあるかもしれません。(IEは未確認です)

動作確認

Windows10Chrome, Firefox, Edge
iOS13Safari, Chrome
iOS12Safari

さいごに

走れメロスについて

青空文庫で読めます。


解説して思ったけどボタン(<label>)のclass名.tab-labelじゃなくて.tab-buttonにすればよかったかな…

タイトルとURLをコピーしました