汎用性の高いタブ切り替えを元にスライドして切り替わる動きを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.ボタン・アクティブなタブより前のボタン */ .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%)
(右寄り)を指定する事でアクティブに切り替えた時の左右の流れを表現します。
アンダーバーの切り替わりも同じ要領で左右に寄せます。
寄せる方向は前述のコンテンツとは左右逆にします。
アクティブタブより前のアンダーバーにはtranslateX(100%)
(右寄り)、後にはtranslateX(-100%)
(左寄り)を指定する事でアクティブに切り替えた時の左右の流れを表現出来ます。
※各ボタンのアンダーバーは疑似要素.tab-label::after
で表現しています。
そして非アクティブタブのコンテンツにはheight:0;
を指定し、且つoverflow
はvisible
(初期値/指定なし)とします。
hidden
(はみ出たコンテンツを隠す)を指定するとコンテンツが即座に隠れてしまい左右にスライドする表現が見えなくなるので注意。
さらに、アクティブタブのコンテンツにorder:1;
と指定。
これでアクティブコンテンツが最後尾になるためすべてのコンテンツが重なり縦軸が揃います。
画像だけでは少し分かりづらいですが、order
を指定した事でコンテンツの並び順が変更されています。
- コンテンツ1
order:0
(非アクティブ高さ0) - コンテンツ3
order:0
(非アクティブ高さ0) - コンテンツ2
order:1
(アクティブ高さauto)
フェードイン/フェードアウトは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は未確認です)
動作確認
Windows10 | Chrome, Firefox, Edge |
---|---|
iOS13 | Safari, Chrome |
iOS12 | Safari |
さいごに
走れメロスについて
青空文庫で読めます。
解説して思ったけどボタン(<label>
)のclass名.tab-label
じゃなくて.tab-button
にすればよかったかな…
完全に左右に並んでスクロールしているように見せたい場合は
-30%/30%
を-100%/100%
にしてopacity:0;
を削除でいい感じになるかと思います。