基本のタブ切り替え
<div class="js-tab-container tab-container">
<ul class="tab-menu flex text-center">
<li class="js-tab-menu__item tab-menu__item is-active">
タブ1
</li>
<li class="js-tab-menu__item tab-menu__item">
タブ2
</li>
<li class="js-tab-menu__item tab-menu__item">
タブ3
</li>
</ul>
<div class="tab-content">
<div class="js-tab-content__item tab-content__item is-active">
コンテンツ1
</div>
<div class="js-tab-content__item tab-content__item">
コンテンツ2
</div>
<div class="js-tab-content__item tab-content__item">
コンテンツ3
</div>
</div>
</div>
アクティブなタブと表示コンテンツにis-active
classを付与しておく。
/* 一部抜粋 */
.tab-content__item {
opacity: 0;
height: 0;
overflow: hidden;
}
.tab-content__item.is-active {
opacity: 1;
transition: opacity .4s ease-in-out;
overflow: visible;
height: auto;
padding: 20px;
border: 2px solid #aaa;
}
コンテンツの表示・非表示はdisplay
プロパティが一般的だが、タブ切り替えでふわっと表示させる効果を付けるためopacity
プロパティを変更することで実装した。
- 非表示コンテンツ:透明度
0
、高さ0
、コンテンツからはみ出す領域を隠す(overflow: hidden
) - 表示コンテンツ(
is-active
class):透明度1
、高さauto
、はみ出す領域を表示(overflow: visible
) padding
プロパティはis-active
の状態の時のみ設定する(display:block
と違って、overflow: hidden
ではpadding
領域が隠れない)。
$(function() {
$('.js-tab-menu__item').on('click',function() {
const index = $('.js-tab-menu__item').index(this);
$('.js-tab-menu__item, .js-tab-content__item').removeClass('is-active');
$(this).addClass('is-active');
$('.js-tab-content__item').eq(index).addClass('is-active');
});
});
- タブ
.js-tab-menu__item
をクリックしたときに、クリックしたタブ(this
)の順番(index
)を取得 - 全てのタブとタブコンテンツから
is-active
classを削除 - クリックしたタブとそのタブに対応するタブコンテンツに
is-active
classを付与する
これでひとまず、タブ切り替えが実装された。
しかしこのコードでは、複数設置した場合に挙動がおかしくなる。以下はその例である。1個目のタブコンテナ内のタブをクリックすると、2個目のタブコンテナ内コンテンツも反応して非表示になる。js-tab-content__item
classが付与されたすべてのタブコンテンツが連動してしまうからだ。
複数タブに対応したタブ切り替え
$(function() {
$('.js-tab-menu__item').on('click',function() {
const tabGroup = $(this).parents('.js-tab-group');
const tabMenu = tabGroup.find('.js-tab-menu__item');
const tabContent = tabGroup.find('.js-tab-content__item');
tabMenu.removeClass('is-active');
$(this).addClass('is-active');
const index = tabMenu.index(this);
tabContent.removeClass('is-active');
tabContent.eq(index).addClass('is-active');
});
});
tabGroup
:クリックしたタブおよびコンテンツの全体を囲む祖先要素js-tab-group
tabMenu
:祖先要素内のタブjs-tab-menu__item
tabContent
:祖先要素内のコンテンツjs-tab-content__item
祖先要素に遡って指定することでタブ切り替えの挙動が同一コンテナ内に制限するができ、複数タブコンテナへの対応が可能となる。
タブコンテンツ内からタブ切り替え(複数タブ対応)
需要がどれだけあるかは不明だが、タブコンテンツ内のアンカーをクリックして、次のタブを選択・対象のコンテンツを表示することもできる。
$(function() {
...(略)
// 追加
$('.js-anchor').on('click', function() {
const tabGroup = $(this).parents('.js-tab-group');
const tabContent = $(this).parents('.js-tab-content__item');
const index = tabContent.index();
const tabMenu = tabGroup.find('.js-tab-menu__item');
const position = $('.js-tab-group').offset().top;
tabMenu.removeClass('is-active');
tabMenu.eq(index).next().addClass('is-active');
tabContent.removeClass('is-active').next().addClass('is-active');
$('html, body').animate({scrollTop: position - 20 }, 500, "swing");
});
});
コンテンツ内アンカーのclassをjs-anchor
とする。
- 変数
tabContent
:アンカーの親であるコンテンツ - 変数
index
:親コンテンツの順番を取得 - すべてのタブとコンテンツから
is-active
classを削除 - 親タブの次のタブとそれに対応するコンテンツに
is-active
classを付与
スマホ版の場合はコンテンツが長くなるので、アンカークリックで次のタブを選択したときに、タブコンテンツ上部位置までスクロールするようにした。
- 変数
position
:js-tab-group
の位置を取得(offset().top
) scrollTop
で対象の位置までスクロールする(js-tab-group
より20px上の位置になるよう調整)
var
とは異なり、const
はスコープ内でのみ有効な変数なので、先ほどのスクリプトと変数名が同じでも問題はない(が、紛らわしいと感じる場合は別の変数名にした方がいいかもしれない)。