よく見かけるタブメニューは、ブロックの上部にメニュー。その下にコンテンツがある形態が多いですが、この記事では、縦ではなく左にメニュー。右にコンテンツで横の水平方向のタブをJavaScriptを使って作っています。
コードは、ネイティブなJavaScriptで、かつ同じページに複数個のタブを設置しても動くコードです。
最後までご覧いただけたら嬉しいです。
.querySelectorAll()
JavaScriptの .querySelectorAll()
は、CSSセレクタで指定ができるメソッドです。
ANDやORの検索も可能で、:hover
や:active
の擬似要素にも対応しています。
これ以外にも、JavaScriptには .getElementById()
や、.getElemetnsByClassName()
で、HTML要素を取得できるメソッドはありすが、この.querySelectorAll()
は、jQueryを扱うときのような記述で、HTML要素をセレクタ指定することができるのも特徴です。
水平方向メニューのタブ
早速サンプルです。いわゆるタブのUIで、左にメニュー・右にメニュークリックで表示されるコンテンツブロックの横並びタブです。
Section1-1
山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな
Section1-2
山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな
Section1-3
山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。
メニュークリックで表示されるコンテンツ部分のアニメーションは、CSSで作っています。
実装の手順と方法
コードの解説の前に、実装の手順と方法について解説します。
はじめに、設置したいHTMLへ以下のコードを記述します。
<section class="someTabs" data-tabs>
<nav class="tabs__nav">
<div class="tabs__btnBlock">
<a href="#" class="tabs__item active" data-tab><span>Section1-1</span></a>
<a href="#" class="tabs__item" data-tab><span>Section1-2</span></a>
<a href="#" class="tabs__item" data-tab><span>Section1-3</span></a>
</div>
</nav>
<div class="tabs__body">
<div class="tabs__content active" data-tab-content><p>Section1-1</p><p>山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな</p></div>
<div class="tabs__content" data-tab-content><p>Section1-2</p><p>山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな</p></div>
<div class="tabs__content" data-tab-content><p>Section1-3</p><p>山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。</p></div>
</div>
</section>
次に、JavaScriptのコードをページに記述します。
コードは <body>〜</body>
で、</body>
の閉じタグ(クロージングタグ)の前に記述しましょう。
const tabsElems = document.querySelectorAll("[data-tabs]");
if(tabsElems.length > 0){
for (let i = 0; i < tabsElems.length; i++) {
let tab = tabsElems[i];
let tabBtnElems = tab.querySelectorAll("[data-tab]");
let tabContentElems = tab.querySelectorAll("[data-tab-content]");
for (let i = 0; i < tabBtnElems.length; i++) {
let tabBtn = tabBtnElems[i];
let tabContent = tabContentElems[i];
tabBtn.addEventListener("click", (e) => {
e.preventDefault();
for (let i = 0; i < tabBtnElems.length; i++) {
tabBtnElems[i].classList.remove('active');
tabContentElems[i].classList.remove('active');
}
tabBtn.classList.add('active');
tabContent.classList.add('active');
});
}
}
}
最後に、CSSを記述します。
.someTabs {
display: flex;
justify-content: flex-start;
}
/* 左側 */
.tabs__nav {
width: 200px;
display: flex;
flex-direction: column;
}
.tabs__btnBlock {
border: solid 1px #eee;
display: flex;
flex-direction: column;
border-right: none;
height: 100%;
}
.tabs__item {
background: #FFF;
height: calc(100% / 3);
line-height: 1;
display: block;
border-bottom: solid 1px #eee;
position: relative;
}
.tabs__item span {
position: absolute;
top: 50%;
transform: translateY(-50%);
padding-left: 20px;
}
.tabs__item:last-child {
border-bottom: none;
}
.tabs__item:hover {
text-decoration: none;
opacity: 0.8;
}
.tabs__item.active {
background: linear-gradient( 45deg , #bdb9ff, #67b8ff);
color: #FFF;
}
.tabs__item.active:after {
content: "";
position: absolute;
top: 50%;
left: 100%;
margin-top: -10px;
border: 10px solid transparent;
border-left: 10px solid #74b8ff;
}
/* 右側 */
.tabs__body {
width: calc(100% - 200px);
padding: 25px 35px;
border: solid 1px #eee;
background: #FFF;
min-height: 380px;
}
[data-tab-content]{
display: none
}
[data-tab-content].active{
display: block;
}
.tabs__content p {
margin: 0;
}
.tabs__content.active {
animation: fadeInLeft 0.7s ease 0s 1 normal;
}
/* FadeIn */
@keyframes fadeInLeft {
0% {
opacity: 0;
transform: translateX(-30px);
}
100% {
opacity: 1;
}
}
@media screen and (max-width: 767px) {
/* (ここにモバイル用スタイルを記述) */
.tabs__nav {
width: 115px;
}
.tabs__body {
width: calc(100% - 115px);
padding: 15px 10px 15px 20px;
min-height: auto;
}
.tabs__content {
max-height: 350px;
overflow-y: scroll;
padding-right: 5px;
}
.tabs__content::-webkit-scrollbar {
width: 10px;
}
.tabs__content::-webkit-scrollbar-thumb {
background: #666;
}
::-webkit-scrollbar-track {
background-color: #f0f0f0;
}
}
これで完成です。
ざっくりとしたコードの解説
コードは、HTML・JavaScript・CSSの3種類です。順に解説していきます。
HTML
HTMLは、親要素の section
タグに「data-tabs」。タブのボタンにあたる a
タグに「data-tab」。タブの中身に「data-tab-content」の属性をそれぞれ付与して、全体を作ります。
<section class="someTabs" data-tabs>
<nav class="tabs__nav">
<div class="tabs__btnBlock">
<a href="#" class="tabs__item active" data-tab><span>Section1-1</span></a>
<a href="#" class="tabs__item" data-tab><span>Section1-2</span></a>
<a href="#" class="tabs__item" data-tab><span>Section1-3</span></a>
</div>
</nav>
<div class="tabs__body">
<div class="tabs__content active" data-tab-content><p>Section1-1</p><p>山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな</p></div>
<div class="tabs__content" data-tab-content><p>Section1-2</p><p>山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな</p></div>
<div class="tabs__content" data-tab-content><p>Section1-3</p><p>山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。山路を登りな山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通せば窮屈だ。とかくに人の世は住みにくい。住みにくさが高じると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟った時、詩が生れて、画が出来る。</p></div>
</div>
</section>
これら3つの属性は、後述するJavaScriptとCSS共に連動するので、コードを書き換えて利用する場合は、それぞれ注意して記述するようにしましょう。
JavaScript
JavaScriptは、まずはじめに .querySelectorAll
で親要素に当たる「data-tabs」の属性を持つ要素を全て取得します。
const tabsElems = document.querySelectorAll("[data-tabs]");
if(tabsElems.length > 0){
for (let i = 0; i < tabsElems.length; i++) {
let tab = tabsElems[i];
let tabBtnElems = tab.querySelectorAll("[data-tab]");
let tabContentElems = tab.querySelectorAll("[data-tab-content]");
for (let i = 0; i < tabBtnElems.length; i++) {
let tabBtn = tabBtnElems[i];
let tabContent = tabContentElems[i];
tabBtn.addEventListener("click", (e) => {
e.preventDefault();
for (let i = 0; i < tabBtnElems.length; i++) {
tabBtnElems[i].classList.remove('active');
tabContentElems[i].classList.remove('active');
}
tabBtn.classList.add('active');
tabContent.classList.add('active');
});
}
}
}
そして、取得した「data-tabs」の属性を持つ親要素の中の「data-tab」と、「data-tab-content」の要素をループで回していき、タブのメニューにあたる「data-tab」を .addEventListener
のクリックイベントで、各class名の付け替えでタブの表示・非表示を行います。
ループを複数回まわす為コード量は比較的多めです。
CSS
CSSは、水平のレイアウトの為定番のFlexboxを使って、配置を決めます。配置ができたら、その中の要素のプロパティを書いていきます。
.someTabs {
display: flex;
justify-content: flex-start;
}
/* 左側 */
.tabs__nav {
width: 200px;
display: flex;
flex-direction: column;
}
.tabs__btnBlock {
border: solid 1px #eee;
display: flex;
flex-direction: column;
border-right: none;
height: 100%;
}
.tabs__item {
background: #FFF;
height: calc(100% / 3);
line-height: 1;
display: block;
border-bottom: solid 1px #eee;
position: relative;
}
.tabs__item span {
position: absolute;
top: 50%;
transform: translateY(-50%);
padding-left: 20px;
}
.tabs__item:last-child {
border-bottom: none;
}
.tabs__item:hover {
text-decoration: none;
opacity: 0.8;
}
.tabs__item.active {
background: linear-gradient( 45deg , #bdb9ff, #67b8ff);
color: #FFF;
}
.tabs__item.active:after {
content: "";
position: absolute;
top: 50%;
left: 100%;
margin-top: -10px;
border: 10px solid transparent;
border-left: 10px solid #74b8ff;
}
/* 右側 */
.tabs__body {
width: calc(100% - 200px);
padding: 25px 35px;
border: solid 1px #eee;
background: #FFF;
min-height: 380px;
}
[data-tab-content]{
display: none
}
[data-tab-content].active{
display: block;
}
.tabs__content p {
margin: 0;
}
.tabs__content.active {
animation: fadeInLeft 0.7s ease 0s 1 normal;
}
/* FadeIn */
@keyframes fadeInLeft {
0% {
opacity: 0;
transform: translateX(-30px);
}
100% {
opacity: 1;
}
}
@media screen and (max-width: 767px) {
/* (ここにモバイル用スタイルを記述) */
.tabs__nav {
width: 115px;
}
.tabs__body {
width: calc(100% - 115px);
padding: 15px 10px 15px 20px;
min-height: auto;
}
.tabs__content {
max-height: 350px;
overflow-y: scroll;
padding-right: 5px;
}
.tabs__content::-webkit-scrollbar {
width: 10px;
}
.tabs__content::-webkit-scrollbar-thumb {
background: #666;
}
::-webkit-scrollbar-track {
background-color: #f0f0f0;
}
}
タブの表示・非表示はJavaScriptで「.active」のclass名をコントロールして行います。
さいごに
水平方向のタブは、昨今のモバイルファーストの流れだとあまり使う場面がないかもしれませんが、ぜひ参考にしてみてください。