スキップしてメイン コンテンツに移動

JavaScriptのonclickとclassListとsetTimeoutを使ったアニメーション付きの開閉ボタンのプログラム

サイトのハンバーガーメニュー(≡)などのコンテンツを出し入れできる開閉ボタンをJavaScriptで作成するプログラムには二つの大きな部分がある。

  1. 要素のクリックを判定する
  2. コンテンツに表示変更を行う

JavaScriptのプログラムではクリックを判定するhtmlの要素を取得した上で、イベントハンドラのonclickからさらに表示変更を行うコンテンツにcssのdisplayのnone(非表示)のようなデザインのclassをclassListプロパティのtoggle()(交互)メソッド、またはadd()とremove()(追加と削除)の二つのメソッドで付け替えて開閉ボタンを作成することができる。

色鮮やかな黄と橙と青と緑と赤の五つのドアが並んでいる

開閉ボタンのJavaScriptの基本的なソースコード

JavaScriptのonclickハンドラとclassListのtoggle()メソッドを使う。

ハンバーガーメニューのサンプル

(開閉ボタン)

※押すとメニュー画面が開閉する。

html

<span class="svg-button">
<svg class="hamburger-menu-1" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 8h22M4 15h22M4 22h22" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(開閉ボタン)</text></svg>
</span>

<div class="sub-1 hidden">
<ul><li>アイテム①</li><li>アイテム②</li><li>アイテム③</li></ul>
</div>

css

.svg-button svg {cursor:pointer;}
.hidden {display:none;}
.sub-1 {width:200px;background-color:#e6e6fa;margin-top:1em;margin-bottom:1em;}
.sub-1 ul {list-style:none;padding:1em;}

JavaScript

var hbm1 = document.getElementsByClassName("hamburger-menu-1")[0];
var sb1 = document.getElementsByClassName("sub-1")[0];
hbm1.onclick = function(){
sb1.classList.toggle("hidden");
}

JavaScriptの要素のクリックを判定するonclickプロパティは三つの仕方で、使える。サンプルは他にhtmlの開閉ボタンに関数名のonclick属性を追加したり、JavaScriptでaddEventListener()メソッドの引数にclickを入れたりして書き換えられる。

メニューを元のコンテンツに重ねる場合はcssのpositionで位置取りを変えて同じところに合わせながらz-indexで重なる順番を変えて上に乗せたりすると良いと思う。

htmlのonclick属性を使う場合

html

<span class="svg-button">
<svg onclick="menu()" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 8h22M4 15h22M4 22h22" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(開閉ボタン)</text></svg>
</span>

<div class="sub-1 hidden">
<ul><li>アイテム①</li><li>アイテム②</li><li>アイテム③</li></ul>
</div>

css

.svg-button svg {cursor:pointer;}
.hidden {display:none;}
.sub-1 {width:200px;background-color:#e6e6fa;margin-top:1em;margin-bottom:1em;}
.sub-1 ul {list-style:none;padding:1em;}

JavaScript

var sb1 = document.getElementsByClassName("sub-1")[0];
function menu(){
sb1.classList.toggle("hidden");
}

クリックを判定する三つの方法ではhtmlのonclick属性の使用は推奨されない。

これらの属性を使うことは避けるべきです。これは HTML を巨大化し可読性を下げます。情報と振る舞いの関心事が正しく分離されておらず、発見が困難なバグを生み出します。その上に、Event 属性の使い方はほとんどの場合、Wndow オブジェクト上のグローバル関数にスクリプトを晒す原因になります。これはグローバルの名前空間を汚染します。

onclick属性はクリックを判定したいhtmlの特定の要素に記載できるから分かり易くて便利かも知れないけれどもJavaScriptのプログラムとしては他の方法を使った方が無難なんだ。

JavaScriptのaddEventListener()メソッドを使う場合

html

<span class="svg-button">
<svg class="hamburger-menu-1" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 8h22M4 15h22M4 22h22" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(開閉ボタン)</text></svg>
</span>

<div class="sub-1 hidden">
<ul><li>アイテム①</li><li>アイテム②</li><li>アイテム③</li></ul>
</div>

css

.svg-button svg {cursor:pointer;}
.hidden {display:none;}
.sub-1 {width:200px;background-color:#e6e6fa;margin-top:1em;margin-bottom:1em;}
.sub-1 ul {list-style:none;padding:1em;}

JavaScript

var hbm1 = document.getElementsByClassName("hamburger-menu-1")[0];
var sb1 = document.getElementsByClassName("sub-1")[0];
hbm1.addEventListener("click", function(){
sb1.classList.toggle("hidden");
}, false);

または

function menu(){
sb1.classList.toggle("hidden");
}
var hbm1 = document.getElementsByClassName("hamburger-menu-1")[0];
var sb1 = document.getElementsByClassName("sub-1")[0];
hbm1.addEventListener("click", menu, false);

onclickハンドラからの関数処理とどう違うのか。

一度に1つのオブジェクトに割り当てることができる onclick ハンドラは1つだけです。より柔軟性があるため、代わりにEventTarget.addEventListener()メソッドを使用することをお勧めします。

GlobalEventHandlers.onclick via MDN

addEventListener()メソッドは第二引数にコールバック関数を取って一度にプログラムの複数の箇所で関数処理を起動できるからいつでも使い易い。

onclickハンドラはプログラムに記載された箇所でしか機能しないので、せめて他で必要ない場合に使わないと煩雑になってしまう。

htmlとcssのclassの切り替えにadd()とremove()を使う

サンプルのソースコードではコンテンツを非表示にするcssのdisplayのnoneのclassの付け外しによってハンバーガーメニューの開閉ボタンを動作させている。JavaScriptはclassListプロパティのtoggle()メソッドを使うけれども他にadd()メソッドとremove()メソッドでも同じようにプログラムを組めるんだ。

(開くボタン)

※ボタン毎にメニュー画面が開くか閉じる。

html

<span class="svg-button">
<svg class="hamburger-menu-2" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 8h22M4 15h22M4 22h22" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(開くボタン)</text></svg>
</span>

<div class="sub-2 hidden">
<span class="svg-button">
<svg class="close-button-2" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 4L26 26M26 4L4 26" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(閉じるボタン)</text></svg>
</span>
<ul><li>アイテム①</li><li>アイテム②</li><li>アイテム③</li></ul>
</div>

css

.svg-button svg {cursor:pointer;}
.hidden {display:none;}
.sub-2 {width:200px;background-color:#e6e6fa;margin-top:1em;margin-bottom:1em;padding:1em;}
.sub-2 ul {list-style:none;padding-left:0;}

JavaScript

var hbm2 = document.getElementsByClassName("hamburger-menu-2")[0];
var sb2 = document.getElementsByClassName("sub-2")[0];
var clb2 = document.getElementsByClassName("close-button-2")[0];
hbm2.onclick = function(){
hbm2.classList.add("hidden");
sb2.classList.remove("hidden");
};
clb2.onclick = function(){
hbm2.classList.remove("hidden");
sb2.classList.add("hidden");
}

開閉ボタンにclassListプロパティのadd()メソッドとremove()メソッドを使う場合、コンテンツの切り替えのスイッチを二つに分けるデザインに向いている。

スイッチが一つでコンテンツの表示と非表示の切り替えを行うには何等かの仕方で、コンテンツの状況を判定するための条件分岐のifを通さなくてはならない。するとclassがあれば付けて、なければ付けないという条件分岐を予め含んでいるtoggle()メソッドを使う方がプログラムは簡素化される。

アニメーション付きの開閉ボタンの作成

表示するコンテンツにcssのanimationを付けておく。

スライダーの開くアニメーションのサンプル

(開閉ボタン)

※押すとメニュー画面が開閉する。

html

<span class="svg-button">
<svg class="hamburger-menu-3" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 8h22M4 15h22M4 22h22" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(開閉ボタン)</text></svg>
</span>

<div class="sub-3 hidden">
<ul><li>アイテム①</li><li>アイテム②</li><li>アイテム③</li></ul>
</div>

css

.svg-button svg {cursor:pointer;}
.hidden {display:none;}
.sub-3 {width:200px;background-color:#e6e6fa;margin-top:1em;margin-bottom:1em;animation:slidein .2s ease-out;}
.sub-3 ul {list-style:none;padding:1em;}
@keyframes slidein {from {transform:translateX(-100%)} to {transform:translateX(0)}}

JavaScript

var hbm3 = document.getElementsByClassName("hamburger-menu-3")[0];
var sb3 = document.getElementsByClassName("sub-3")[0];
hbm3.onclick = function(){
sb3.classList.toggle("hidden");
}

スライダーの場合のcssのanimationにはコンテンツを移動させるtransformのtranslateX(横方向)やtranslateY(縦方向)が使い易いと思う。

もう一つ良く使われるcssはopacityで、コンテンツの透明度を変えて徐々に浮かび上がらせたり(値は0から1)、沈み込ませたり(値は1から0)するデザインだ。

他にも縦に伸縮するアコーディオンメニューならばheightなどでコンテンツの高さを変化させると良い。

参考:高さ可変!CSSアニメーションでなめらかアコーディオン

JavaScriptの開閉ボタンで厄介なのはcssのdisplayのnoneによってコンテンツを消すときにアニメーションがかからない。

スライダーだとサンプルでもそうだけれども開くときはゆっくり動かすデザインが可能でも閉じるときは全く動かすデザインを付けられないからコンテンツは瞬時に消える他はなくなる。

setTimeout()メソッドでコンテンツの非表示を遅らせる

開閉ボタンの消すときのアニメーションは先んじてコンテンツを非表示にするdisplayのnoneのかかりをJavaScriptのsetTimeout()(停止)メソッドで時間差を付けて反対に遅らせると大丈夫なんだ。

スライダーの開いて閉じるアニメーションのサンプル

(開閉ボタン)

※押すとメニュー画面が開閉する。

html

<span class="svg-button">
<svg class="hamburger-menu-4" width="142" height="30" viewBox="0 0 142 30"><rect width="28" height="28" x="1" y="1" stroke="#8b008b" stroke-width="1px" fill="none"/><path d="M4 8h22M4 15h22M4 22h22" stroke="#8b008b" stroke-width="3px"/><text x="31" y="15" font-size="16px" dominant-baseline="central">(開閉ボタン)</text></svg>
</span>

<div class="sub-4 hidden">
<ul><li>アイテム①</li><li>アイテム②</li><li>アイテム③</li></ul>
</div>

css

.svg-button svg {cursor:pointer;}
.hidden {display:none;}
.sub-4 {width:200px;background-color:#e6e6fa;margin-top:1em;margin-bottom:1em;animation:slidein .2s ease-out;}
.sub-4 ul {list-style:none;padding:1em;}
.slideout {animation:slideout .2s ease-out;}
@keyframes slidein {from {transform:translateX(-100%)} to {transform:translateX(0)}}
@keyframes slideout {from {transform:translateX(0%)} to {transform:translateX(-100%)}}

JavaScript

var hbm4 = document.getElementsByClassName("hamburger-menu-4")[0];
var sb4 = document.getElementsByClassName("sub-4")[0];
hbm4.onclick = function(){
if(sb4.classList.contains("hidden")){
sb4.classList.remove("hidden", "slideout");
} else {
sb4.classList.add("slideout");
setTimeout(function(){
sb4.classList.add("hidden");
}, 200);
}}

開閉ボタンの開くときと閉じるときのプログラムの内容が実質的に変わるから取り込んだ要素が非表示のclassを持つかどうかのcontains()メソッドなどを使った条件分岐のifをクリック判定後の関数処理に配置する必要がある。

サンプルはコンテンツに非表示のclassがあればremove()メソッドで非表示と同時に二回目以降に付けられるスライダーの閉じるアニメーションのclassを外す。コンテンツに非表示のclassがなければ先ずは開閉ボタンの閉じるアニメーションのclassを付けてからsetTimeout()メソッドで時間差をおいて後らせてから非表示のclassを付ける。

プログラムの注意点としてsetTimeout()メソッドのdelay(遅延)と閉じるアニメーションのanimation-duration(継続時間)を合わせなくてはならない。さもないとアニメーションが終わる前にコンテンツが消えたり、アニメーションが終わった後にコンテンツが現れたりしてしまう。

cssのanimationとJavaScriptのsetTimeout()の設定時間は単位が違う。サンプルでは「.2s」と「200」に指定されている。前者のanimation-durationは一秒単位、後者のdelayは千分の一秒単位(値に単位の表記は不要)になっている。かりに同じ時間を指定するとしても両方が同じ記述にはならない。

detailsタグでサイトに開閉メニューを作る

コメント

些細な日常の人気の投稿

PlayストアでAndroidアプリのダウンロードが非常に遅い場合の打開策

イメージ

ジャパネットたかたの丸尾詩織の商品説明に気持ちが入っていて素晴らしい理由

イメージ