幅に入り切らないタブは、右端の「その他 ▾」に自動で格納されます。
ウィンドウ幅を変えると、入る本数を自動で数え直します(ResizeObserver)。
「その他」の中の項目を選ぶと、そのパネルが表示されます。
タブが全部入る幅なら「その他」ボタンは自動で消えます。
メニューの項目は、タブのテキストからJSが自動生成しています。
<!-- タブは何本でもOK。入り切らない分は「その他」へ自動格納 -->
<div class="bnto-tab12" data-bnto-overflowtab>
<div class="tl" role="tablist" aria-label="サンプルタブ">
<button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
<button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
<button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
<button type="button" class="tb" role="tab" aria-selected="false">タブ4</button>
<button type="button" class="tb" role="tab" aria-selected="false">タブ5</button>
<!-- 溢れたタブの受け皿(メニューの中身はJSが自動生成) -->
<div class="mr">
<button type="button" class="more" aria-haspopup="true" aria-expanded="false">その他 <span aria-hidden="true">▾</span></button>
<div class="menu" hidden></div>
</div>
</div>
<div class="pn is-active" role="tabpanel">パネル1の中身</div>
<div class="pn" role="tabpanel">パネル2の中身</div>
<div class="pn" role="tabpanel">パネル3の中身</div>
<div class="pn" role="tabpanel">パネル4の中身</div>
<div class="pn" role="tabpanel">パネル5の中身</div>
</div>
.bnto-tab12 {
--c1: hsl(174 62% 38%); /* アクセント色 */
--dur: .3s; /* 切替スピード */
background: #fff; border: 2px solid #DFE9F1;
border-radius: 14px; /* メニューがはみ出すので overflow:hidden は付けない */
}
.bnto-tab12 .tl { display: flex; flex-wrap: nowrap; border-bottom: 2px solid #DFE9F1; }
.bnto-tab12 .tb {
flex: none; padding: 11px 12px; white-space: nowrap;
background: none; border: none;
border-bottom: 3px solid transparent; margin-bottom: -2px;
font: inherit; font-weight: 800; color: #8a847a; cursor: pointer;
transition: color var(--dur), border-color var(--dur);
}
.bnto-tab12 .tb.is-active { color: var(--c1); border-bottom-color: var(--c1); }
.bnto-tab12 .tb[hidden] { display: none; } /* 溢れたタブは非表示に */
.bnto-tab12 .mr { position: relative; margin-left: auto; flex: none; }
.bnto-tab12 .mr[hidden] { display: none; }
.bnto-tab12 .more {
padding: 11px 12px; background: none; border: none; white-space: nowrap;
font: inherit; font-weight: 800; color: var(--c1); cursor: pointer;
}
/* 溢れタブを納めるドロップダウン */
.bnto-tab12 .menu {
position: absolute; right: 4px; top: calc(100% + 2px); z-index: 20;
min-width: 130px; background: #fff;
border: 2px solid #DFE9F1; border-radius: 12px; padding: 5px;
box-shadow: 0 12px 28px rgba(0,0,0,.14);
}
.bnto-tab12 .menu[hidden] { display: none; }
.bnto-tab12 .mi {
display: block; width: 100%; padding: 8px 10px;
background: none; border: none; border-radius: 8px;
font: inherit; font-weight: 700; text-align: left; cursor: pointer;
}
.bnto-tab12 .mi:hover { background: #F0F6FB; }
.bnto-tab12 .mi.is-active { color: var(--c1); font-weight: 800; }
.bnto-tab12 .pn { display: none; padding: 13px 16px; line-height: 1.75; }
.bnto-tab12 .pn.is-active { display: block; }
// 溢れタブの「その他」格納:入り切らないタブをドロップダウンへ(ResizeObserver)
(function () {
document.querySelectorAll('[data-bnto-overflowtab]').forEach(function (root) {
if (root.dataset.bntoInit) return;
root.dataset.bntoInit = '1';
var list = root.querySelector('.tl');
var tabs = Array.prototype.slice.call(root.querySelectorAll('.tb'));
var panels = Array.prototype.slice.call(root.querySelectorAll('.pn'));
var wrap = root.querySelector('.mr');
var more = root.querySelector('.more');
var menu = root.querySelector('.menu');
var cur = 0;
tabs.forEach(function (t, i) { if (t.classList.contains('is-active')) cur = i; });
function closeMenu() { menu.hidden = true; more.setAttribute('aria-expanded', 'false'); }
function select(i) {
cur = i;
tabs.forEach(function (t, j) {
t.classList.toggle('is-active', j === cur);
t.setAttribute('aria-selected', j === cur ? 'true' : 'false');
panels[j].classList.toggle('is-active', j === cur);
});
Array.prototype.forEach.call(menu.children, function (m) {
m.classList.toggle('is-active', +m.dataset.i === cur);
});
closeMenu();
}
// 入り切らないタブを数えて「その他」メニューへ移す
function layout() {
closeMenu();
tabs.forEach(function (t) { t.hidden = false; }); // いったん全部表示して幅を測る
menu.innerHTML = '';
wrap.hidden = true;
var max = list.clientWidth;
var used = 0, fit = tabs.length, i;
for (i = 0; i < tabs.length; i++) {
used += tabs[i].offsetWidth;
if (used > max) { fit = i; break; }
}
if (fit >= tabs.length) return; // 全部入るなら「その他」は不要
wrap.hidden = false;
max = list.clientWidth - wrap.offsetWidth; // 「その他」の幅ぶんを差し引く
used = 0; fit = 0;
for (i = 0; i < tabs.length; i++) {
used += tabs[i].offsetWidth;
if (used > max) break;
fit++;
}
tabs.forEach(function (t, j) {
t.hidden = j >= fit;
if (j >= fit) {
var m = document.createElement('button');
m.type = 'button';
m.className = 'mi' + (j === cur ? ' is-active' : '');
m.dataset.i = j;
m.textContent = t.textContent;
m.addEventListener('click', function () { select(j); });
menu.appendChild(m);
}
});
}
tabs.forEach(function (t, i) {
t.addEventListener('click', function () { select(i); });
});
more.addEventListener('click', function () {
var open = menu.hidden;
menu.hidden = !open;
more.setAttribute('aria-expanded', open ? 'true' : 'false');
});
document.addEventListener('click', function (e) {
if (!wrap.contains(e.target)) closeMenu();
});
// 幅の変化を監視して自動で入れ替える
if (window.ResizeObserver) new ResizeObserver(layout).observe(list);
select(cur);
layout();
});
})();