ボタン
画面クリック=ボタン(1個)の例
Ex.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
//画面にあるメニューを1個取ってくる
const menu = document.querySelector('.menu');
menu.classList.toggle('is-open');
});
});
- クリック発生=必ず“メニューボタンに対するクリック”
- なら、何をする?(処理):今の状態を逆の状態にしたい
- 「状態」→JSはこれを 見た目じゃなくて印(クラスetc)で判断する
種類は1つ、ボタンが複数ある
w.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
//クリックしたのはmenu用のボタンや!
const btn = e.target.closest('.menu-btn');
const wrapper = btn.closest('li');//このボタンを含む li を取る
const menu = wrapper.querySelector('.menu');//その li の中の menu を取る
menu.classList.toggle('is-open');//menuを開閉
});
});
- クリック発生=必ず“どれかのメニューボタンに対するクリック”
- 複数ある中の、どのボタンがクリックされたかを特定したい
- そのボタンに“対応するメニュー”を見つける
- 処理
どのボタンか識別
- ボタンは複数ある
- それぞれ「枠(li)」の中に入っている
- クリックされた場所から上にたどっていけば、必ず“そのボタン本体”に行き着く
- 例
Ex.htm
<li>
<a class="menu-btn">
<svg>
<path></path>
</svg>
</a>
<div class="menu"></div>
</li>
- ボタン本体:
<a class="menu-btn"> - その中に svg
- その中に path
- ユーザーは見た目として「ボタン」をクリックしている
実際に「クリックされる場所」はどこ?
- 人間の感覚
👉「ボタンをクリックした」 - ブラウザの現実
👉一番内側の要素がクリックされた - 例:アイコンの線をクリックした場合
<li>
<a class="menu-btn">
<svg>
<path> ← 👈 実際にクリックされた場所(e.target)
</svg>
</a>
</li>
- JS「path がクリックされたよ」
- でも知りたいのは「どのボタンが押されたか」
- だから 変換が必要:クリックされた場所から、親へ、親へと進む
- これがclosest
a (★ ボタン本体)
↑
svg
↑
path:e.target (クリック地点)
たとえばA,B,Cの三つのボタンを識別するには
E.htm
<ul>
<li>
<a class="menu-btn" data-name="A">
<span>A</span>
</a>
</li>
<li>
<a class="menu-btn" data-name="B">
<span>B</span>
</a>
</li>
<li>
<a class="menu-btn" data-name="C">
<span>C</span>
</a>
</li>
</ul>
クリック(例)
li
↑
a (★ ボタン本体)
↑
span:e.target (クリック地点)
E.js
const clicked = e.target; // span
const btn = e.target.closest('.menu-btn'); // a[data-name="A"]
const whichBtn = btn.dataset.name; // "A"
console.log(whichBtn); // A
対応するメニューとは
E.htm
<li>
<button class="menu-btn">ボタン</button>
<div class="menu">メニュー</div>
</li>
- 「ボタンとメニューは、同じ li の中にある」
- 👉 つまり対応関係は「距離」じゃなく「構造」で決める
クリック対象がメニューボタン以外にもある
- メニュー以外のボタンとかaタグ以外のクリックとか
- その中で先ほどのメニューボタンがクリックされたら
e.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
const btn = e.target.closest('.menu-btn');
if(!btn) return; //たどってもメニューボタンクラスもつaタグがない
const wrapper = btn.closest('li');
const menu = wrapper.querySelector('.menu');
menu.classList.toggle('is-open');
});
});
メニューが存在しないなど想定外の構造のボタン
e.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
const btn = e.target.closest('.menu-btn');
if(!btn) return; //クリックがメニューボタンへのものじゃない
const wrapper = btn.closest('li');
if(!wrapper)return; //このメニューボタンに li がない
const menu = wrapper.querySelector('.menu');
if(!menu)return; //li の中の menu がない
menu.classList.toggle('is-open');
});
});
メニュー外クリックで閉じる
- 今まで:クリックが
- メニューボタンか? → btn
- ちがうか? → return
- つまり 「メニューボタンのクリック」だけは処理できている
- 今から:メニューが開いている状態で、メニューでもボタンでもない場所をクリックしたら、メニューを閉じたい
= 「閉じる条件」は“ボタンじゃない”だけでは足りない
r.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
const btn = e.target.closest('.menu-btn');//a.menu-btn
if(btn){
const wrapper = btn.closest('li');//このボタンを含む li を取る
if(!wrapper)return;
const menu = wrapper.querySelector('.menu');//その li の中の menu を取る
if(!menu)return;
menu.classList.toggle('is-open');//menuを開閉
return;
}
if(e.target.closest('.menu')){
return;
}
//開いてるメニュー1個だけ
const menu = document.querySelector('.menu.is-open');
if(menu){//開いてるメニューがあれば閉じる処理
menu.classList.remove('is-open');
}
});
});
外クリックで開いてるメニュー閉じる→開いてるメニューはここでは1個のみが前提
menu A(open) ← ← ← これ取る
menu B(closed)
menu C(closed)
画面がクリックされたとき、考えることは3つ
① それはメニューボタンか?
- YES → 処理(toggle)
- NO → 次へ
② それはメニューの中か?
- YES → 何もしない(操作中だから)
- NO → 次へ
③ それ以外か?
YES → メニューを閉じる
「メニューの中で起きたか?」をどう調べる?
- 「クリックされた場所から上にたどって、.menu に行き着くか?」
例:メニュー内をクリックした場合
[li]
[div.menu]
[ul]
[li]
[a] ← クリック(e.target)
e.js
e.target.closest('.menu') //→ ある ✅
例:メニュー外をクリックした場合
[header]
[div.container] ← クリック
e.js
e.target.closest('.menu') //→ null ❌
メニュー外クリックで閉じる(複数のメニューが開いてる場合)
menu A(open)
menu B(open)
さっきのコードのままだとAしか閉じない
ex.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
const btn = e.target.closest('.menu-btn');//a.menu-btn
if(btn){
const wrapper = btn.closest('li');//このボタンを含む li を取る
if(!wrapper)return;
const menu = wrapper.querySelector('.menu');//その li の中の menu を取る
if(!menu)return;
menu.classList.toggle('is-open');//menuを開閉
return;
}
if(e.target.closest('.menu')){
return;
}
document.querySelectorAll('.is-open').forEach((menu)=>{
menu.classList.remove('is-open');
});
});
});
メニューは同時に1つしか開けないようにする&ボタン再クリックで閉じる
Ex.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click',(e)=>{
const btn = e.target.closest('.menu-btn');
if(btn){
const wrapper = btn.closest('li');
if(!wrapper)return;
const menu = wrapper.querySelector('.menu');
if(!menu)return;
// ✅ 排他トグル
const isOpen = menu.classList.contains('is-open');//クリック時そいつは開いてた?閉じてた?
document.querySelectorAll('.menu.is-open').forEach(el=>{
el.classList.remove('is-open');//全部閉じる
});
if(!isOpen){//もともと閉じてたなら開く
menu.classList.add('is-open');
}
return;
}
// メニュー内クリックは無視
if(e.target.closest('.menu')){
return;
}
// 外クリック → 全部閉じる
document.querySelectorAll('.menu.is-open').forEach(menu=>{
menu.classList.remove('is-open');
});
});
});
排他トグルを使う
1個だけのメニューだったとき👇
Ex.js
menu.classList.toggle('is-open');//問題なし
- 他のmenuが存在しない
- 状態は1つだけ
- 複数メニューになるとこうはうまくいかない
→このセクションについてはこちらを参照:
data-action によるボタン分岐
E.htm
<a data-action="menu-toggle">メニューボタン</a>
<a data-action="modal-open" data-target="#">モーダルボタン</a>
ex.js
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]');
const action = btn?.dataset.action;
// ======================
// ① メニュー用
// ======================
if (action === 'menu-toggle') {
const wrapper = btn.closest('li');
if (!wrapper) return;
const menu = wrapper.querySelector('.menu');
if (!menu) return;
const isOpen = menu.classList.contains('is-open');
document.querySelectorAll('.menu.is-open').forEach(el => {
el.classList.remove('is-open');
});
if (!isOpen) {
menu.classList.add('is-open');
}
return;
}
// ======================
// ② モーダルとか(例)
// ======================
if (action === 'modal-open') {
//そのボタンに書かれている“対象(target)”を使って、開くべきモーダルを探す
// つまり、「○○っていうモーダルを開け」
const target = document.querySelector(btn.dataset.target);
if (target) {//そのモーダルがちゃんと見つかった?
target.classList.add('is-open');//そのモーダルを表示状態にする
}
return;
}
// ======================
// ③ メニュー内クリックは無視
// ======================
if (e.target.closest('.menu')) {
return;
}
// ======================
// ④ それ以外 → メニュー閉じる
// ======================
document.querySelectorAll('.menu.is-open').forEach(menu => {
menu.classList.remove('is-open');
});
});
});
すべてのボタンにdata-actionをつける⇔data-actionがあるならボタン
Ex.js
//?:「btn があるなら進む」「無いならそこで止まる
//ボタンならそのactionを取得
const action = btn?.dataset.action;
ボタン
構造
構造で結びついてる
e.htm
<li>
<a class="menu-btn"></a>
<div class="menu"></div>
</li>
「このボタンのmenuは、同じliの中」
探し方
ex.js
//li
wrapper.querySelector('.menu')
モーダル
構造
構造が離れてる
EX.htm
<header>
<a data-action="modal-open" data-target="#modalodal1">...</div>
</body>
👉 ボタンとモーダルは全然別の場所
探し方
- data-target→ボタンにかいてあるセレクタを使って操作対象の要素を取得
- 例:
- クリックされたボタンのdataset.target → "#modal1"
- ボタンクリックで表示したいメニューは id='modal1'
↓
document.querySelector("#modal1")
:<div id="modal1">(メニュー本体) を取得
👉 名前で探す(ID)
