JavaScriptの .classList.toggle
は、class名のあるなしの状態を判断して変更してくれる便利なメソッドで、個人的にもよく使っています。
この記事では、この .classList.toggle
を使ってレスポンシブのヘッダーを作ってみます。
デザインはすごく標準的なヘッダーなので、使う場合はカスタマイズしてご利用ください。
是非、最後までご覧いただけたら嬉しいです。
.classList.toggle
JavaScriptの .classList.toggle
は、指定した要素にclass名が存在していれば削除して、無ければ追加するメソッドです。
document.body.classList.toggle("class名");
classを追加する classList.add
と、classを削除する classList.remove
で同じclass名を切り替える場合は、classList.toggle
を使用するとコードがスッキリします。
スマホの場合はドロワメニューになるヘッダーのサンプル
早速サンプルです。この記事にも、当該のヘッダーを実装していますがパソコンでご覧いただくと標準的なヘッダーです。
一方、スマホで閲覧するとメニューが全てドロワメニューに格納され、左上のトグルボタンをタップすることで左側からドロワーメニューが表示されます。
LOGO
Google Chromeデベロッパーツールのスマホビューなどでご覧ください。
実装の手順と方法
コードの解説の前に、実装の手順と方法について解説します。
はじめに、以下のHTMLを記述します。ヘッダーなので、場所は header
タグの中に記述しましょう。
<div id="header">
<div class="overlay"></div>
<div class="headerInner">
<p class="headerLogo">LOGO</p>
<nav class="navLinks">
<div class="toggle"> <span id="deleteconpo" class="toggler"></span> </div>
<ul class="parentMenu">
<li><a href="#">MENU1</a>
<ul class="navSubMenu">
<li><a href="#">SubMenu1</a></li>
<li><a href="#">SubMenu2</a></li>
<li><a href="#">SubMenu3</a></li>
</ul>
</li>
<li><a href="#">MENU2</a></li>
<li><a href="#">MENU3</a>
<ul class="navSubMenu">
<li><a href="#">SubMenu1</a></li>
<li><a href="#">SubMenu2</a></li>
<li><a href="#">SubMenu3</a></li>
</ul>
</li>
<li><a href="#">MENU4</a></li>
</ul>
</nav>
</div>
</div>
次に、JavaScriptのコードをページに記述します。
コードは <body>〜</body>
で、</body>
の閉じタグ(クロージングタグ)の前に記述しましょう。
// ドロワー
const toggler = document.querySelector(".toggle");
window.addEventListener("click", event => {
if(event.target.className == "toggle" || event.target.className == "toggle") {
document.body.classList.toggle("show-nav");
document.getElementById("deleteconpo").classList.toggle("deleteclass")
} else if (event.target.className == "overlay") {
document.body.classList.remove("show-nav");
document.getElementById("deleteconpo").classList.toggle("deleteclass")
}
});
// スクロールで可変
const header = document.getElementById("header");
window.addEventListener('scroll', function(){
if (document.body.scrollTop > 120 || document.documentElement.scrollTop > 120) {
header.classList.add('resize');
} else {
header.classList.remove('resize');
}
});
最後に、CSSを記述します。コード量が多いですが、そのまま利用する場合はコードをまるっとコピペします。
/* header親 */
div#header {
position: absolute;
top: 0;
width: 100%;
left: 0;
z-index: 9999;
background: #fff;
box-shadow: 0 3px 6px rgb(0 0 0 / 18%);
margin: 0;
}
/* header中身 */
.headerInner {
width: 100%;
max-width: 1130px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
transition: 0.2s ease-in-out;
}
/* LOGO */
p.headerLogo {
margin: 0;
font-size: 1.3rem;
}
/* トグル */
.toggle {
display: none;
transition: 0.2s ease-in-out;
}
/* MENU */
nav.navLinks a {
text-decoration: none;
}
nav.navLinks .parentMenu {
border: none;
list-style: none;
display: flex;
gap: 20px;
margin: 0;
padding: 0;
}
.navLinks .parentMenu li {
margin: 0;
padding: 0;
position: relative;
}
.navLinks .parentMenu li a {
padding: 0 15px;
line-height: 70px;
display: inline-block;
color: #313131;
position: relative;
}
/* 親メニューのhover時の下線 */
.navLinks .parentMenu li a:after {
content: '';
position: absolute;
bottom: 0;
left: 0%;
width: 100%;
height: 3px;
background: #f0db40;
transition: all .3s;
transform: scale(0, 1);
transform-origin: center top;
}
.navLinks .parentMenu li a:hover:after {
transform: scale(1, 1);
}
/* サブメニュー */
nav.navLinks .parentMenu li ul.navSubMenu {
display: none;
border-radius: 0;
position: absolute;
top: 100%;
left: 0;
margin: 0;
padding: 0;
background: #FFF;
transition: 0.3s ease-in-out;
list-style: none;
}
.navLinks .parentMenu li ul.navSubMenu li a {
line-height: 1;
padding: 20px 25px;
border-bottom: solid 1px #eee;
}
ul.navSubMenu li:last-child a {
border: none;
}
nav.navLinks .parentMenu li:hover ul.navSubMenu {
display: flex;
flex-direction: column;
box-shadow: 0 3px 5px rgb(0 0 0 / 22%);
gap: 0;
}
nav.navLinks .parentMenu li ul.navSubMenu li a:after {
content: none;
}
ul.navSubMenu li a:hover {
background: #fafafa;
}
@media screen and (max-width: 767px) {
/* (ここにモバイル用スタイルを記述) */
div#header {
position: fixed;
}
.headerInner {
height: 50px;
}
.overlay {
width: 100%;
height: 100vh;
position: fixed;
left: 0;
top: 0;
background-color: rgba(0,0,0,.3);
z-index: 190;
opacity: 0;
visibility: hidden;
transition: all 200ms ease-in;
}
nav.navLinks {
width: 270px;
height: 100vh;
background-color: #FFF;
left: -270px;
top: 0;
position: fixed;
padding: 0;
transition: all 200ms ease-in-out;
z-index: 199;
display: block;
}
nav.navLinks .parentMenu {
border: none;
padding: 0;
flex-direction: column;
}
.toggle {
position: relative;
left: 100%;
width: 45px;
height: 50px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
span.toggler,
span.toggler:before,
span.toggler:after {
content: '';
display: block;
height: 3px;
width: 22px;
border-radius: 3px;
background-color: #707070;
position: absolute;
pointer-events: none;
transition: 0.3s ease-in-out;
}
span.toggler:before{
bottom: 8px;
}
span.toggler:after {
top: 8px;
}
span.deleteclass {
background-color: transparent;
}
span.deleteclass::before {
bottom: 0;
transform: rotate(45deg);
}
span.deleteclass::after {
top: 0;
transform: rotate(-45deg);
}
p.headerLogo {
position: absolute;
left: 50%;
transform: translateX(-50%);
line-height: 1;
}
.navLinks ul li {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
.navLinks .parentMenu li a {
padding: 10px 20px;
display: block;
color: #313131;
font-size: 1rem;
text-decoration: none;
transition: all 200ms ease;
line-height: 1;
display: block;
}
.navLinks ul li a:hover {
background-color: #f1f1f1;
}
/* Show Nav */
.show-nav .navLinks {
left: 0;
box-shadow: 0 2px 4px rgba(0,0,0,.6);
}
.show-nav .overlay {
opacity: 1;
visibility: visible;
}
/* SUBMENU */
nav.navLinks .parentMenu li ul.navSubMenu {
display: block;
position: relative;
}
/* スクロールで可変 */
div#header.resize .headerInner {
height: 35px;
}
div#header.resize .toggle {
height: 35px;
}
div#header.resize .toggle .toggler, div#header.resize .toggle .toggler:before, div#header.resize .toggle .toggler:after {
height: 2px;
width: 18px;
}
div#header.resize span.toggler:before{
bottom: 6px;
}
div#header.resize span.toggler:after {
top: 6px;
}
div#header.resize span.deleteclass::before {
bottom: 0;
}
div#header.resize span.deleteclass::after {
top: 0;
}
}
ざっくりとしたコードの解説
コードはHTML・JavaScript・CSSの3種です。ざっくりですが、順に解説していきます。
HTML
HTMLは、id名が「header」の親要素を作り、ドロワーを開いた時のに画面を覆い隠す「overlay」のclass名が付く要素と、ロゴやメニューを格納する「headerInner」のclass名が付く要素を作ります。
<div id="header">
<div class="overlay"></div>
<div class="headerInner">
<p class="headerLogo">LOGO</p>
<nav class="navLinks">
<div class="toggle"> <span id="deleteconpo" class="toggler"></span> </div>
<ul class="parentMenu">
<li><a href="#">MENU1</a>
<ul class="navSubMenu">
<li><a href="#">SubMenu1</a></li>
<li><a href="#">SubMenu2</a></li>
<li><a href="#">SubMenu3</a></li>
</ul>
</li>
<li><a href="#">MENU2</a></li>
<li><a href="#">MENU3</a>
<ul class="navSubMenu">
<li><a href="#">SubMenu1</a></li>
<li><a href="#">SubMenu2</a></li>
<li><a href="#">SubMenu3</a></li>
</ul>
</li>
<li><a href="#">MENU4</a></li>
</ul>
</nav>
</div>
</div>
各メニューは、主要なナビゲーションを表すHTMLタグの nav
タグの中に記述します。
これらのHTMLコードは全てヘッダーの要素なので、まるッと header
タグの中に記述しましょう。
JavaScript
JavaScriptは大きく分けて、スマホで表示される「ドロワー」と、スクロールした時にclass名を付け替えする「スクロール可変class」の2種類です。
// ドロワー
const toggler = document.querySelector(".toggle");
window.addEventListener("click", event => {
if(event.target.className == "toggle" || event.target.className == "toggle") {
document.body.classList.toggle("show-nav");
document.getElementById("deleteconpo").classList.toggle("deleteclass")
} else if (event.target.className == "overlay") {
document.body.classList.remove("show-nav");
document.getElementById("deleteconpo").classList.toggle("deleteclass")
}
});
// スクロールで可変
const header = document.getElementById("header");
window.addEventListener('scroll', function(){
if (document.body.scrollTop > 120 || document.documentElement.scrollTop > 120) {
header.classList.add('resize');
} else {
header.classList.remove('resize');
}
});
ドロワーの表示・非表示は、.addEventListener
のクリックで body
タグへ「show-nav」へのclass名の付け替えをして、ドロワーの挙動を作っています。
CSS
CSSはレスポンシブ対応の為、メディアクエリの max-width: 767px
で表示の出しわけもあるので、コード量も多めです。
/* header親 */
div#header {
position: absolute;
top: 0;
width: 100%;
left: 0;
z-index: 9999;
background: #fff;
box-shadow: 0 3px 6px rgb(0 0 0 / 18%);
margin: 0;
}
/* header中身 */
.headerInner {
width: 100%;
max-width: 1130px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
transition: 0.2s ease-in-out;
}
/* LOGO */
p.headerLogo {
margin: 0;
font-size: 1.3rem;
}
/* トグル */
.toggle {
display: none;
transition: 0.2s ease-in-out;
}
/* MENU */
nav.navLinks a {
text-decoration: none;
}
nav.navLinks .parentMenu {
border: none;
list-style: none;
display: flex;
gap: 20px;
margin: 0;
padding: 0;
}
.navLinks .parentMenu li {
margin: 0;
padding: 0;
position: relative;
}
.navLinks .parentMenu li a {
padding: 0 15px;
line-height: 70px;
display: inline-block;
color: #313131;
position: relative;
}
/* 親メニューのhover時の下線 */
.navLinks .parentMenu li a:after {
content: '';
position: absolute;
bottom: 0;
left: 0%;
width: 100%;
height: 3px;
background: #f0db40;
transition: all .3s;
transform: scale(0, 1);
transform-origin: center top;
}
.navLinks .parentMenu li a:hover:after {
transform: scale(1, 1);
}
/* サブメニュー */
nav.navLinks .parentMenu li ul.navSubMenu {
display: none;
border-radius: 0;
position: absolute;
top: 100%;
left: 0;
margin: 0;
padding: 0;
background: #FFF;
transition: 0.3s ease-in-out;
list-style: none;
}
.navLinks .parentMenu li ul.navSubMenu li a {
line-height: 1;
padding: 20px 25px;
border-bottom: solid 1px #eee;
}
ul.navSubMenu li:last-child a {
border: none;
}
nav.navLinks .parentMenu li:hover ul.navSubMenu {
display: flex;
flex-direction: column;
box-shadow: 0 3px 5px rgb(0 0 0 / 22%);
gap: 0;
}
nav.navLinks .parentMenu li ul.navSubMenu li a:after {
content: none;
}
ul.navSubMenu li a:hover {
background: #fafafa;
}
@media screen and (max-width: 767px) {
/* (ここにモバイル用スタイルを記述) */
div#header {
position: fixed;
}
.headerInner {
height: 50px;
}
.overlay {
width: 100%;
height: 100vh;
position: fixed;
left: 0;
top: 0;
background-color: rgba(0,0,0,.3);
z-index: 190;
opacity: 0;
visibility: hidden;
transition: all 200ms ease-in;
}
nav.navLinks {
width: 270px;
height: 100vh;
background-color: #FFF;
left: -270px;
top: 0;
position: fixed;
padding: 0;
transition: all 200ms ease-in-out;
z-index: 199;
display: block;
}
nav.navLinks .parentMenu {
border: none;
padding: 0;
flex-direction: column;
}
.toggle {
position: relative;
left: 100%;
width: 45px;
height: 50px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
span.toggler,
span.toggler:before,
span.toggler:after {
content: '';
display: block;
height: 3px;
width: 22px;
border-radius: 3px;
background-color: #707070;
position: absolute;
pointer-events: none;
transition: 0.3s ease-in-out;
}
span.toggler:before{
bottom: 8px;
}
span.toggler:after {
top: 8px;
}
span.deleteclass {
background-color: transparent;
}
span.deleteclass::before {
bottom: 0;
transform: rotate(45deg);
}
span.deleteclass::after {
top: 0;
transform: rotate(-45deg);
}
p.headerLogo {
position: absolute;
left: 50%;
transform: translateX(-50%);
line-height: 1;
}
.navLinks ul li {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
.navLinks .parentMenu li a {
padding: 10px 20px;
display: block;
color: #313131;
font-size: 1rem;
text-decoration: none;
transition: all 200ms ease;
line-height: 1;
display: block;
}
.navLinks ul li a:hover {
background-color: #f1f1f1;
}
/* Show Nav */
.show-nav .navLinks {
left: 0;
box-shadow: 0 2px 4px rgba(0,0,0,.6);
}
.show-nav .overlay {
opacity: 1;
visibility: visible;
}
/* SUBMENU */
nav.navLinks .parentMenu li ul.navSubMenu {
display: block;
position: relative;
}
/* スクロールで可変 */
div#header.resize .headerInner {
height: 35px;
}
div#header.resize .toggle {
height: 35px;
}
div#header.resize .toggle .toggler, div#header.resize .toggle .toggler:before, div#header.resize .toggle .toggler:after {
height: 2px;
width: 18px;
}
div#header.resize span.toggler:before{
bottom: 6px;
}
div#header.resize span.toggler:after {
top: 6px;
}
div#header.resize span.deleteclass::before {
bottom: 0;
}
div#header.resize span.deleteclass::after {
top: 0;
}
}
ドロワーのトグルボタンをタップすると「✖️」になるのも、CSSで記述しています。
さいごに
ヘッダーはいろんなデザインがあり、いろんなデザイン・レイアウトのページがあるので万能でハマるヘッダーのデザインというのはないと思いますが、自分の型のようなものを色々持っておくと便利です。
この記事ではドロワータイプのヘッダーですが、是非参考にしてみてください。