【連載】CPIエバンジェリストのお悩み相談室
JavaScriptでクリックした要素を検出し、classを付与する方法(後編)

©StockUnlimited

2017年7月20日
TEXT:阿部 正幸(KDDIウェブコミュニケーションズ)
前編では、ボタンを3つ並べ、クリックするとボタンにactive クラスを追加したり、削除したりする方法をお伝えしました。今回は、クリックしたボタン以外にactiveクラスが付いていたら、削除する方法をご紹介します。

>>> 前編はこちら
ちなみに現在のソースコードはこちらです。

[現在のソースコード]<!DOCTYPE html>
<html>
<head>
  <title>TEST</title>
  <meta charset="UTF-8">
  <style type="text/css">
    .btn{
       width: 200px;
       height: 30px;
       margin: 5px; outline: none;
      }
      .active{
          background-color: #666;
      }
  </style>
</head>
<body>
  <button class="btn">ボタン1</button>
  <button class="btn">ボタン2</button>
  <button class="btn">ボタン3</button>

<script>
  // ボタンのDOM要素を取得
  var btn = document.getElementsByClassName('btn');

  // ボタンの個数分ループ
  // 変数「i」に現在のループ回数が代入される
  for (var i = btn.length - 1; i >= 0; i--) {
    // 各ボタンをイベントリスナーに登録
    btn[i].addEventListener("click", function(){
     // activeクラスの追加と削除
     // thisは、クリックされたオブジェクト
     this.classList.toggle('active');
    });
  }
</script>
</body>
</html>
■Step1: 
ボタンの個数分ループしているfor文から、addEventListnerを外します。

<script>
  // ボタンのDOM要素を取得
  var btn = document.getElementsByClassName('btn');

  // ボタンの個数分ループ
  // 変数「i」に現在のループ回数が代入される
  for (var i = btn.length - 1; i >= 0; i--) {
    btnAction(btn[i],i);
  } 

  function btnAction(btnDOM,btnId){
    // 各ボタンをイベントリスナーに登録
    btnDOM.addEventListener("click", function(){
    // activeクラスの追加と削除
    // thisは、クリックされたオブジェクト
    this.classList.toggle('active');
  });
 }
</script>
btnAction() 関数を作成します。
その中に、イベントリスナーと、classList.toggleを入れています。

For文には、作成したbtnAction()関数の呼び出しを追加します。 btnAction()呼び出し時に、各ボタンの要素 btn[i]と、現在のループ番号 i を引数に渡しています。クリック時のアクションを関数化しましたが、処理の動きは変わりません。

■Step2: 
ボタンの個数分ループしているfor文から、addEventListnerを外します。

btnAction() 関数を修正します。
function btnAction(btnDOM,btnId){
  // 各ボタンをイベントリスナーに登録
  btnDOM.addEventListener("click", function(){
  // activeクラスの追加と削除
  // thisは、クリックされたオブジェクト
  this.classList.toggle('active');

  // クリックされていないボタンにactiveがついていたら外す
  for (var i = btn.length - 1; i >= 0; i--) {
  if(btnId !== i){
    if(btn[i].classList.contains('active')){
      btn[i].classList.remove('active');
      }
     }
    }
  })
}
再度ボタンの個数分ループします。

for (var i = btn.length - 1; i >= 0; i--) {

「btnId」 はbtnAction()の呼び出し時に受け取った番号で、クリックされたボタンの番号が入っています。「i」には現在のループ番号入ります。よって、下記のif文はクリックされたボタン以外の場合に真になります。

if(btnId !== i){

クリックされた以外のボタンの場合、classList.contains(‘active’) を使い、ボタンにactiveクラスが付与されていないか確認します。

if(btn[i].classList.contains('active')){
btn[i].classList.remove('active');

} activeクラスが付与されていたら、classList.remove('active')でactiveクラスを削除します。

以上で、前回から行っているプログラムは終了しました。
完成したコードはこちらです。

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <style type="text/css">
    .btn{
       width: 200px;
       height: 30px;
       margin: 5px;
       outline: none;
    }
    .active{
        background-color: #666;
    }
  </style>
</head>
<body>
    <button class="btn">ボタン1</button>
    <button class="btn">ボタン2</button>
    <button class="btn">ボタン3</button>

<script>
  // ボタンのDOM要素を取得
  var btn = document.getElementsByClassName('btn');

  // ボタンの個数分ループ
  // 変数「i」に現在のループ回数が代入される
  for (var i = btn.length - 1; i >= 0; i--) {
    btnAction(btn[i],i);
  }

  function btnAction(btnDOM,btnId){
    // 各ボタンをイベントリスナーに登録
    btnDOM.addEventListener("click", function(){
    // activeクラスの追加と削除
    // thisは、クリックされたオブジェクト
    this.classList.toggle('active');

    // クリックされていないボタンにactiveがついていたら外す
    for (var i = btn.length - 1; i >= 0; i--) {
      if(btnId !== i){
        if(btn[i].classList.contains('active')){
          btn[i].classList.remove('active');
        }
      }
    }
  })
}
</script>

</body>
</html>

●ステップアップ練習問題 
では、最後にステップアップとして次の要件に合わせてソースコードを修正してみましょう。

練習問題(1)
クリックした時に、buttonタグに対して activeクラスを追加しました。下記のようにbuttonタグの中にspanタグを追加し、spanタグに対してactiveクラスを追加してみましょう。

<button class="btn"><span>ボタン1</span></button>

ヒント:
buttonタグの下のspanタグのDOM要素取得は、btnDOM.addEventListener("click", function(){の下に console.dir(this);を設置すると分かりやすいですが、childrenプロパティに直下のタグがDOMとして取得することができます。

練習問題(2)
現在ボタンのクリックを検出しクラスの追加と削除をしています。ボタン以外がクリックされた時(bodyタグや、その他の要素)に、activeクラスが付いていたら、activeを削除しましょう。

ヒント:
htmlタグに対して、イベントリスナーを登録します。クリック時に呼び出される関数には、MouseEventオブジェクトが引数として受け取れるので、ボタン以外がクリックされたことを判断し、処理を行います。

 var html = document.getElementsByTagName("html");
 html[0].addEventListener("click", function(e){
   // マウスイベントの出力
   console.dir(e);
})

実行結果:


練習問題(1):答え
this.classList.toggle('active'); を
this.children[0].classList.toggle('active'); に変更する。

練習問題(2):答え
var html = document.getElementsByTagName("html");
html[0].addEventListener("click", function(e){
  // HTMLがクリックされたら
  if(e.path[0].nodeName === 'HTML'){
    // ボタンの個数分ループ
    for (var i = btn.length - 1; i >= 0; i--) {
      if(btn[i].classList.contains('active')){
        btn[i].classList.remove('active');
      }
    }
  }
})
上記コードをスクリプトの最後に追加する。

e.path で、クリックされた階層のパスを確認することができます。例えばボタンがクリックされた場合、e.path[0] は、ボタン要素が入ります。

さて、練習問題は解けましたでしょうか。読んでもわからないという方のために、レンタルサーバー「CPI」のスタッフブログでJavascriptのオブジェクトを解説した動画を公開しました!よろしければ、こちらも合わせてご覧ください。

url. http://shared-blog.kddi-web.com/webinfo/220

>>>「CPIエバンジェリストのお悩み相談室」の目次はこちら

[筆者プロフィール]
阿部 正幸(あべ まさゆき) | KDDIウェブコミュニケーションズ/エバンジェリスト
システム開発会社で大規模なシステム開発を経験後、Web制作会社でプログラマー兼ディレクターとして従事。その後、KDDIウェブコミュニケーションズに入社、レンタルサーバーCPIのプロダクトマネージャーに就任。ACE01、SmartReleaseをリリース後、現職の「エバンジェリスト」としてWeb制作に関する様々なイベントに登壇。Drupal(g.d.o Japan)日本コミュニティー、HTML5 funなどに所属し、OSSを世に広げる活動やWeb制作に関する情報を発信している。

●CPIフタッフブログ
 URL:http://shared-blog.kddi-web.com/

● CPI LINE@のご案内
http://shared-blog.kddi-web.com/other/215