<main class="main-wrapper">
<div class="tab-ui">
<ul class="tablist" role="tablist">
<li role="presentasion">
<a href="#content1" class="tab" role="tab" aria-controls="content1" aria-selected="true">タブ1</a>
</li>
<li role="presentasion">
<a href="#content2" class="tab" role="tab" aria-controls="content2">タブ2</a>
</li>
<li role="presentasion">
<a href="#content3" class="tab last-of-type" role="tab" aria-controls="content3">タブ3</a>
</li>
</ul>
<div class="tabpanel-wrap">
<section id="content1" class="tabpanel" role="tabpanel" aria-hidden="false">
<h2>タブコンテンツ1</h2>
<ul class="tabpanel__list">
<li>
<strong>・マークアップについて</strong><br>
タブパネル(コンテンツ)内に見出しが存在する場合はsection要素、存在しない場合はdiv要素でラップしてください
</li>
<li>
<strong>・キーボード操作について</strong><br>
選択操作をしない場合、フォーカスは文書構造に沿って上/左の順で当たります(Webページでの基本的な動き)。<br>
当該タブをクリックした場合、次に表示タブパネル内のリンクなどインタラクティブ要素にフォーカスします<br>
※隠れたタブパネル内の要素にフォーカスしません(CSS・visibility:
hidden;の仕様)
</li>
<li>
<strong>・JavaScriptまたはCSSをオフにしても閲覧可能</strong><br>
JSやCSSをユーザー側設定でオフにしてもコンテンツを見られるつくりにしています。<br>
JSオフの場合タブはページ内リンクとして機能し、コンテンツは縦レイアウトになります<br>
※どのような閲覧条件でも、文書情報(コンテンツ)の表示を優先する方針
</li>
</ul>
<a href="#" class="tabpanel__link">ダミーのリンクです</a>
</section>
<section id="content2" class="tabpanel" role="tabpanel" aria-hidden="true">
<h2>タブコンテンツ2</h2>
<ul class="tabpanel__list">
<li>
<strong>・タブUIの欠点</strong><br>
タブUIの欠点は各タブパネルのコンテンツ量を同等にしないと、下部に大きく余白ができたり、<br>
レイアウト上の不ぞろい感を生んだりすることです。また、JSなどで高さを補う必要や手間も生じます。<br>
なので、なるべく同等の情報量を持つようにそれぞれをコンテンツ設計してください<br>
※「不ぞろいでも別に気にならない」という方もいるかもしれませんが...
</li>
<li>
<strong>・タブUIを考える</strong><br>
そもそもタブUIであることの利点とは何でしょうか。<br>
情報が並列化され構造把握しやすい、スクロールよりクリック/タップのほうが操作コストが低い、などが挙げられるでしょうか。<br>
しかしコンテンツは常に見えていたほうが良く、「隠す」「再表示する」などは逆に認知負荷にもなるとも考えられます。<br>
以上のことから、タブUIはそこまで汎用的とはいえず、使い所をよく検討したいUIです。
</li>
</ul>
</section>
<section id="content3" class="tabpanel" role="tabpanel" aria-hidden="true">
<h2>タブコンテンツ3</h2>
<p class="tabpanel__paragraph">
タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。
</p>
<a href="#" class="tabpanel__link">ダミーのリンクです1</a><br>
<a href="#" class="tabpanel__link">ダミーのリンクです2</a>
</section>
</div>
</div>
<section class="other-content">
<h3>タブUIのコンポーネント外に配置されたコンテンツのサンプルです</h3>
<p>
各タブパネルのラップ要素の高さ(height)値に、各タブパネルのなかで最高値をJavaScriptで付与し、高さ固定しています。そのため、タブUIのあとに続くコンテンツは各タブパネルのコンテンツ量に応じて位置上下しません。
</p>
</section>
</main>
<main class="main-wrapper">
<div class="tab-ui">
<ul class="tablist" role="tablist">
<li role="presentasion">
<a
href="#content1"
class="tab"
role="tab"
aria-controls="content1"
aria-selected="true"
>タブ1</a
>
</li>
<li role="presentasion">
<a
href="#content2"
class="tab"
role="tab"
aria-controls="content2"
>タブ2</a
>
</li>
<li role="presentasion">
<a
href="#content3"
class="tab last-of-type"
role="tab"
aria-controls="content3"
>タブ3</a
>
</li>
</ul>
<div class="tabpanel-wrap">
<section id="content1" class="tabpanel" role="tabpanel" aria-hidden="false">
<h2>タブコンテンツ1</h2>
<ul class="tabpanel__list">
<li>
<strong>・マークアップについて</strong><br>
タブパネル(コンテンツ)内に見出しが存在する場合はsection要素、存在しない場合はdiv要素でラップしてください
</li>
<li>
<strong>・キーボード操作について</strong><br>
選択操作をしない場合、フォーカスは文書構造に沿って上/左の順で当たります(Webページでの基本的な動き)。<br>
当該タブをクリックした場合、次に表示タブパネル内のリンクなどインタラクティブ要素にフォーカスします<br>
※隠れたタブパネル内の要素にフォーカスしません(CSS・visibility:
hidden;の仕様)
</li>
<li>
<strong>・JavaScriptまたはCSSをオフにしても閲覧可能</strong><br>
JSやCSSをユーザー側設定でオフにしてもコンテンツを見られるつくりにしています。<br>
JSオフの場合タブはページ内リンクとして機能し、コンテンツは縦レイアウトになります<br>
※どのような閲覧条件でも、文書情報(コンテンツ)の表示を優先する方針
</li>
</ul>
<a href="#" class="tabpanel__link">ダミーのリンクです</a>
</section>
<section id="content2" class="tabpanel" role="tabpanel" aria-hidden="true">
<h2>タブコンテンツ2</h2>
<ul class="tabpanel__list">
<li>
<strong>・タブUIの欠点</strong><br>
タブUIの欠点は各タブパネルのコンテンツ量を同等にしないと、下部に大きく余白ができたり、<br>
レイアウト上の不ぞろい感を生んだりすることです。また、JSなどで高さを補う必要や手間も生じます。<br>
なので、なるべく同等の情報量を持つようにそれぞれをコンテンツ設計してください<br>
※「不ぞろいでも別に気にならない」という方もいるかもしれませんが...
</li>
<li>
<strong>・タブUIを考える</strong><br>
そもそもタブUIであることの利点とは何でしょうか。<br>
情報が並列化され構造把握しやすい、スクロールよりクリック/タップのほうが操作コストが低い、などが挙げられるでしょうか。<br>
しかしコンテンツは常に見えていたほうが良く、「隠す」「再表示する」などは逆に認知負荷にもなるとも考えられます。<br>
以上のことから、タブUIはそこまで汎用的とはいえず、使い所をよく検討したいUIです。
</li>
</ul>
</section>
<section id="content3" class="tabpanel" role="tabpanel" aria-hidden="true">
<h2>タブコンテンツ3</h2>
<p class="tabpanel__paragraph">
タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。タブコンテンツ3の内容です。
</p>
<a href="#" class="tabpanel__link">ダミーのリンクです1</a><br>
<a href="#" class="tabpanel__link">ダミーのリンクです2</a>
</section>
</div>
</div>
<section class="other-content">
<h3>タブUIのコンポーネント外に配置されたコンテンツのサンプルです</h3>
<p>
各タブパネルのラップ要素の高さ(height)値に、各タブパネルのなかで最高値をJavaScriptで付与し、高さ固定しています。そのため、タブUIのあとに続くコンテンツは各タブパネルのコンテンツ量に応じて位置上下しません。
</p>
</section>
</main>
/* No context defined. */
/* /js/modules/Tab.js */
/**
* Tab UI class
*/
export class Tab {
constructor(selector1, selector2, selector3, selector4) {
this.tabUi = document.querySelector(selector1);
this.tabs = document.querySelectorAll(selector2);
this.tabPanelWrap = document.querySelector(selector3);
this.tabPanels = document.querySelectorAll(selector4);
if(this.tabPanels.length > 0) {
this.tabPanels[0].setAttribute('aria-hidden', 'false');
this.tabPanels[1].setAttribute('aria-hidden', 'true');
this.tabPanels[2].setAttribute('aria-hidden', 'true');
// NodeListを配列に変換
this.tabPanelArray = [].slice.call(this.tabPanels);
this.heightArray = [];
}
}
// tabpanelの高さを配列で返す
getHeight() {
if(this.tabPanels.length > 0) {
for (let i = 0; i < this.tabPanelArray.length; i++) {
this.heightArray[i] = this.tabPanelArray[i].offsetHeight;
// tabPanelHeightにtabpanelの高さの配列を代入
this.tabPanelHeight = this.heightArray;
// tabPanelHeightのなかで最も大きい値を算出し代入
this.tabPanelHeight = Math.max.apply(null, this.tabPanelHeight);
}
return this.tabPanelHeight;
}
}
// 戻り値(算出した最高値)をラップ要素と各tabpanelに高さ付与する
setHeight() {
this.tabPanelHeight = this.getHeight();
if(this.tabPanels.length > 0) {
// tabUI全体の高さをtabpanelの最大値分付与(visivility: hidden;で高さが失われた分)
this.tabPanelWrap.style.height = this.tabPanelHeight + 'px';
}
}
// タブを切り替える
switchTabs() {
for (let i = 0; i < this.tabs.length; i++) {
// tabのクリックイベント
this.tabs[i].addEventListener('click', e => {
e.preventDefault();
for (let i = 0; i < this.tabs.length; i++) {
// tabがaria-selected="true"を持ち、選択状態を示す場合
if (this.tabs[i].hasAttribute('aria-selected', 'true')) {
// そのtabからaria-selected="true"を削除して非選択状態を示す
this.tabs[i].removeAttribute('aria-selected', 'true');
}
if (this.tabPanels[i].hasAttribute('tabindex', '0')) {
this.tabPanels[i].removeAttribute('tabindex', '0');
}
}
// クリックされたtabにaria-selected="true"を付与して選択状態を示す
this.tabs[i].setAttribute('aria-selected', 'true');
let pairedId = document.getElementById(this.tabs[i].getAttribute('aria-controls'));
for (let i = 0; i < this.tabPanels.length; i++) {
// tabpanelがaria-hidden="false"を持ち、表示を示す場合
if (this.tabPanels[i].hasAttribute('aria-hidden', 'false')) {
// そのtabpanelにaria-hidden="true"を付与して非表示を示す
this.tabPanels[i].setAttribute('aria-hidden', 'true');
}
}
// クリックされたtabのaria-control値を参照し、対応するtabpanelに対してaria-hidden="false"を付与して表示を示す
pairedId.setAttribute('aria-hidden', 'false');
pairedId.setAttribute('tabindex', '0');
pairedId.focus();
}, { passive: false });
}
}
throttle() {
window.addEventListener('resize', () => {
setTimeout(() => {
this.setHeight()
}, 50);
});
}
}
/* /js/main.js */
import { Tab } from './modules/Tab.js';
const tab = new Tab('.tab-ui', ".tab[role='tab']", '.tabpanel-wrap', ".tabpanel[role='tabpanel']");
tab.setHeight();
tab.switchTabs();
tab.throttle();
.tab-ui {
position: relative;
width: 100%;
margin: auto;
}
@media print, screen and (min-width:48em) {
.tab-ui {
max-width: 60rem;
}
}
.tab-ui__heading {
margin: 0 auto 1rem;
}
.tablist {
display: flex;
width: auto;
}
/* タブの隣接部分のborderが太くなることが許せない人向け */
/* .tablist>li:not(:first-of-type) .tab {
margin-left: -1px;
} */
.tab {
display: inline-block;
padding: .65rem 1rem;
border-top: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
border-left: 1px solid var(--border-color);
text-align: center;
text-decoration: none;
color: var(--black);
border-radius: 5px 5px 0 0;
/* fade-out effect */
/* transition: background-color .3s ease, color .3s ease; */
/* 選択されているタブのスタイル */
}
@media print, screen and (min-width:48em) {
.tab {
width: auto;
min-width: 12rem;
/* タブのhover/focus状態スタイル */
}
.tab:hover, .tab:focus {
background: #777;
color: var(--white);
/* fade-in effect */
/* transition: background-color .25s ease, color .25s ease; */
}
}
@media print, screen and (max-width:47.96875em) {
.tab {
width: 100%;
}
}
.tab[aria-selected] {
background: var(--black);
color: var(--white);
}
@media print, screen and (max-width:47.96875em) {
.tablist > li {
width: 33.33333%;
}
}
.tabpanel-wrap {
width: 100%;
height: fit-content;
min-height: 150px;
border: 1px solid var(--border-color);
color: var(--black);
overflow: hidden;
}
.tabpanel {
z-index: 0;
width: 100%;
padding: 1rem 2rem 2.5rem;
}
.tabpanel:focus {
outline: none;
}
.tabpanel[aria-hidden="false"] {
position: absolute;
top: 9.5%;
visibility: visible;
}
.tabpanel[aria-hidden="true"] {
position: absolute;
top: 9.5%;
visibility: hidden;
}
.tabpanel__list > li {
margin: .8rem auto auto;
}
.tabpanel__paragraph {
margin: 1.4rem auto 0;
}
.tabpanel__link {
display: inline-block;
width: auto;
margin: 1.4rem auto 0;
}
.other-content {
width: 100%;
max-width: 60rem;
margin: 2rem auto;
padding: 1rem 2rem;
border: 1px solid var(--black);
}
No notes defined.