JavaScriptでDOM操作で頻出するaddEventListenerの基本とさまざまな使い方

2021年1月9日JavaScript

JavaScriptでDOM操作するときに頻出するaddEventListener()は、クリックだけではなく、
いろんなイベントを設定することができます。
簡単なのにできることが格段に増えるので、ぜひ学んでいきましょう。

addEventListener()に深く関わるイベントの伝播については、次の記事を参考にしてみてください。


addEventListener()とthisの関係については、次の記事を参考にしてみてください。

addEventListener()とは

Webサイト上で発生するあらゆるアクションのことをイベント(event)といいます。
たとえば、ホバー、クリックなどです。
そのイベントの状態を監視しているものをイベントリスナー(EventListener)と呼ばれます。
そして、そのイベントを登録することで、イベントが起きたときに処理を加えることができます。
イベントを登録するときに使うのが、addEventListener()です。

addEventListener()の基本的な使い方

addEventListener()を使うことで、特定のアクションをした場合のイベントを作ることができます。
第一引数に、イベントの種類を渡します。
クリックしたとき、ならclickどして、第二引数にはどう処理したいのかを示す関数を書きます。

具体的にみてみましょう。

<h2>イベントの設定</h2>
  <p id="event">ここのテキストが変わります。</p>
  <button>クリック</button>

  'use strict';
  {
    function change() {
      document.getElementById('event').textContent = 'イベントを設定しよう';
    }

    document.querySelector('button').addEventListener('click', change);
  }

また、上のJavaScript文は関数名を省いてアロー関数でまとめることも可能です。


    'use strict';
    {
      document.querySelector('button').addEventListener('click', () => {
        document.getElementById('event').textContent = 'イベントを設定しよう';
      });
    }

 

イベントのいろんな設定方法

イベントの設定方法1:addEventListener()

これは上で紹介した方法となります。
他の方法との比較として例を記載します。


{
  const h1 = document.querySelector('h1');

  //イベント設定方法1
  h1.addEventListener('mouseenter', function (e) {
    // console.log('addEventlistenr');
  });
}

イベントの設定方法2:変数に格納する


{
  const h1 = document.querySelector('h1');

  //イベント設定方法2
  const consoleLogH1 = function (e) {
    console.log('consoleLogH1:addEventlistenr');
  };

  h1.addEventListener('mouseenter', consoleLogH1);
}

コレ自体は特筆すべきことはありませんが、変数に格納できるという利点があるため、addEventListener()では次のような応用的な使い方ができるようになります。

イベントの設定方法3:removeEventListener()を使ってイベントを1回だけ呼び出す

removeEventListener()を使ってイベントを1回だけ呼び出すことができます。
変数に格納することで、以下のようにするとイベントを1回だけ呼び出すことができます。


{
  const h1 = document.querySelector('h1');

  //イベント設定方法3
  const consoleLogH1 = function (e) {
    console.log('removeEventListener()を使ってイベントを1回だけ呼び出す');
    //イベントを取り除く
    h1.removeEventListener('mouseenter', consoleLogH1);
  };

  h1.addEventListener('mouseenter', consoleLogH1);
}

イベントの設定方法4:setTimeout()で時間制限付きのイベントを作成

removeEventListener()とsetTimeout()を使うことで、時間制限付きのイベントを作成できます。


{
  const h1 = document.querySelector('h1');

  //イベント設定方法4
  const consoleLogH1 = function (e) {
    console.log('removeEventListener()とsetTimeout()');
  };

  h1.addEventListener('mouseenter', consoleLogH1);

  //setTimeout()でイベントを取り除く
  setTimeout(() =>
  h1.removeEventListener('mouseenter', consoleLogH1), 3000);
}

イベントの設定方法5:DOM イベントハンドラー


{
const button = document.querySelector("button");
const h1 = document.querySelector("h1");

const changeH1Red = function () {
  h1.style.color = "red";
};
const changeH1BgBlue = function () {
  h1.style.backgroundColor = "blue";
};

// addEventListener()は複数適用できる
button.addEventListener("click", changeH1Red);
button.addEventListener("click", changeH1BgBlue);

// イベントハンドラーは下に書いたものに上書きされる
button.onclick = changeH1Red;
button.onclick = changeH1BgBlue;

}

onclickといったイベントハンドラーに直接登録する方法です。
現在ではaddEventListenerを使うのが主流のため、おすすすめはしません。
addEventListenerは、引数を変更するだけで他のイベントを複数実装できますが、onclickといったイベントハンドラーの場合は上書きされてしまうため、一つしか適用することができません。
addEventListenerのもうひとつの利点は、反応してほしくないときに反応しないように設定するのが容易な点です。上で紹介したremoveEventListener()を使ってイベントを1回だけ呼び出す方法がまさにその実例になります。

イベントの設定方法6:htmlに設定する

{
<!-- イベント設定方法6 -->
<h1 onclick="console.log('HTML内に設定')"></h1>

}

これも柔軟性がないので普通は使わない方法ですが、頭の片隅に置いておくといいでしょう。

addEventListener()のいろんなイベント

どんなイベントがあるかは、MDNのイベントリファレンスを参照するとまとめられています。

一番重要な部分はマウスとキーボードのイベント部分です。

他にもクリップボードやビューポート部分も参考にするといいでしょう。

以下によく使うものの使い方を紹介します。

ダブルクリックに反応するdblclick

dblclickはダブルクリック(double click)に反応します。
dblclickの使い方は、clickのときと基本的に同じです。

HTML
<h2>見出し</h2>
<p id="target">ここの文章がかわります。</p>
<button id="db-btn">ダブルクリック</button>
JavaScript
'use strict';
{
  const textChange = document.querySelector('button');
  textChange.addEventListener('dblclick', ()=> {
    document.getElementById('target').textContent = 'Changed by doubleclick';
  });
}

スマホでタッチしたら反応するtouchstart

clickイベントはモバイルでも反応しますが、タッチされてから300ms後にイベントが発火します。
touchstartはタッチ後即発火のため、モバイルはtouchstartがおすすめです。

class MobileMenu {
  constructor() {
    this.DOM = {};
    this.DOM.btn = document.querySelector('.mobile-menu__btn');
    this.DOM.container = document.querySelector('#global-container');
    this.eventType = this._getEventType();
    this._addEvent();
  }
  _getEventType() {
    //スマホとPCでイベントタイプを切り分ける
    return window.ontouchstart ? 'touchstart' : 'click';
  }
  _toggle() {
    this.DOM.container.classList.toggle('menu-open');
  }
  _addEvent() {
    this.DOM.btn.addEventListener(this.eventType, this._toggle.bind(this));
  }
}

new MobileMenu();

ブラウザがモバイルかデスクトップか判定する際、window.ontouchstartがあればモバイル、そうでなければデスクトップとします。
上では、条件(三項)演算子(ternary conditional operators)で書いています。

マウスの座標を取得するmousemove

mousemoveはマウスの動きに反応します。
関数に引数を渡すと、ブラウザがイベントに関する情報をセットして渡してくれる仕組みになっています。
引数は慣習的にeventのeとします。

addEventListener(‘mousemove’, e => {...});

eventオブジェクトのe

eventオブジェクトのeを使ってe.clientX,e.clientYでブラウザの左上を起点としてX,Y座標になります。

'use strict';
  {
    document.addEventListener('mousemove', e => {
      console.log(`X座標は${e.clientX}で、Y座標は${e.clientY}です。`);
    });
  }

マウスを動かしながらコンソールをみると、動くたびに座標が取得できているのが確認できます。

マウスをホバーすると反応するmouseenterとmouseover

mouseenterとmouseoverはどちらもマウスをホバーした時点で反応するイベントです。
mouseenterとmouseoverは似ているけども、バブリングの有無に違いがあります。
mouseenterはバブリングがありません。
mouseoverはバブリングがあります。

マウスをホバーから外すと反応するmouseleaveとmouseout

mouseenterとmouseoverの反対の動作をするものがあります。
それがmouseleaveとmouseoutです。
mouseleaveがmouseenterの反対で、mouseoutがmouseoverの反対です。
つまり、mouseleaveはバブリングなし、mouseoutはバブリングがあります。

キーボードに関するイベントkeydownとkeyup

keydownはキーボードのキーを押したときにイベントが発火します。keydownはキーをプレスしている間何度もイベントが発火します。
keyupはプレスした指を離したときにイベントが発火します。そのため一度だけイベントが起こります。
e.keyでeventオブジェクトのキーを取得できます。

document.addEventListener(‘keydown’, e => {...});
document.addEventListener(‘keyup’, e => {...});

下の例は、入力したキーをコンソールに取得できるプログラムです。

'use strict';
  {
    document.addEventListener('keydown', e => {
      console.log(`入力したのは${e.key}です。`);
    });
  }

このようにすることで、keycodeを取得することができます。

keydownで一度だけイベントを発火させる

一度だけイベントを発火させたいときに、keyupを使う方法もありますが、keydownのほうがプレスした時点でイベントが発火するので即座に反応してほしい場合に有効です。
keydownを使って1回だけしかイベントを発生させたくない場合は、stateの条件をつけてtrue/falseの条件分岐を追加する方法があります。
たとえば、サーチ画面のoverlayがあり、そのoverlayをsキーで表示して、escキーで閉じたいという場合、次のようにすることができます。

import $ from 'jquery';

class Search {

  // 1. describe and create / initiate our object
  constructor() {
    this.openButton = document.querySelectorAll(".js-search-trigger");
    this.closeButton = document.querySelector(".search-overlay__close");
    this.searchOverlay = document.querySelector(".search-overlay");
    this.isOverlayOpen = false;  //keypressしたときの状態で条件分岐したい
    this.events(); //events()を実行する
  }

  // 2. events (1と2をつなげる部分) 
  events() {
    this.openButton.forEach(el => {
      el.addEventListener("click", e => {
        e.preventDefault();
        this.openOverlay();
      })
    })

    this.closeButton.addEventListener("click", () => this.closeOverlay());
    // sキー(searchできる)とescキー(searchをやめる)を押したときに反応する
    document.addEventListener("keydown", e => this.keyPressDispatcher(e));
  }


  // 3. methods (function, action...)

  keyPressDispatcher(e) {
        // overlayがopenしてない状態で、かつ83=sキーが押されたら次の処理
    if (e.keyCode == 83 && !this.isOverlayOpen) {
      this.openOverlay();
    }

    // overlayがopenしている状態で、かつ27=escキーが押されたら次の処理
    if (e.keyCode == 27 && this.isOverlayOpen) {
      this.closeOverlay();
    }
  }

  openOverlay() {
    this.searchOverlay.classList.add("search-overlay--active")
    this.isOverlayOpen = true //isOverlayOpenの状態をtrueにする
  }

  closeOverlay() {
    this.searchOverlay.classList.remove("search-overlay--active");
    this.isOverlayOpen = false; //isOverlayOpenの状態をfalseにする
  }

export default Search;

DOMContentLoadedとloadの違い

DOMContentLoadedというイベントリスナーは、HTMLをブラウザが解釈して、DOMツリーを作成し終わったタイミングで発火するイベントのことです。
DOMContentLoadedは、HTMLがDOMに変換されたタイミングで発火します。画像の表示などを待つことがないため、イベントが早い段階で発火します。
loadは、HTMLからDOMツリーを作成し終わった後、さらに画像や動画やstyleシート、JSファイルなどすべてのコンテンツをダウンロードし終わった後に発火するイベントのことです。
loadは重たい画像などがあると、それを待って発火することになります。
ユーザーには早い段階で表示することがユーザーエクスペリエンスの向上につながるため、画像のダウンロードを待つ必要がないものについては、DOMContentLoadedを基本的に使うほうがいいでしょう。

document.addEventListener('DOMContentLoaded', ...)
window.addEventListener('load', ...)

基本的に、DOMContentLoadedは、documentに対してイベントを登録するのに対し、loadはwindowに対してイベントを登録します。
なお、DOMContentLoadedの場合は、windowにイベントを登録することが可能です。
しかし、loadはdocumentに対して使用できません。
asyncとdeferのDOMContentLoadedの挙動の違いについては、次の記事を参考にしてみてください。

フォームで使うイベント

フォームでイベントをみてみましょう。

focus / blur

focusはフォーカスがあたったときのイベントです。
blurはfocusがはずれたときのイベントです。

下の例ではinputをクリックするとfocusが反応し、
他の場所をクリックするとfocusが外れてblurが反応ことがコンソールをみるとわかります。

HTML
<form id="form">
  <input type="text" placeholder="クリックしてみましょう">
</form>
JavaScript
'use strict';
  {
    const checkFocusBlur = document.querySelector('input');
      //focus
      checkFocusBlur.addEventListener('focus', () => {
        console.log('focusされています');
      });
  
      //blur
      checkFocusBlur.addEventListener('blur', () => {
        console.log('blur、つまりfocusが外れています');
      });
  }

input / change

次に、同じくフォームで使うイベントであるinputとchangeをみていきましょう。
inputは内容が更新されたとき。つまり入力中のイベントを指します。
changeは更新が確定したときのイベントを指します。入力が終えてフォーカスが外れたときに反応します。

下の例では、inputフォームに入力しているときは、コンソールにinputイベントが反応していることがわかり、
フォーカスを外したときにchangeイベントが反応するのがわかるプログラムです。

HTML
  <form id="form">
    <input type="text" placeholder="入力してみましょう">
  </form>
JavaScript
'use strict';
  {
    const checkInputChange = document.querySelector('input');
      //input
      checkInputChange.addEventListener('input', () => {
        console.log('inputが反応しています');
      });
  
      //change
      checkInputChange.addEventListener('change', () => {
        console.log('changeが反応しました');
      });
  }

文字数を取得する

inputイベントを利用すると、valueプロパティの長さをlengthを使ってテキストの文字数を取得できます。

下の例では、テキストエリアに何文字入力したかがコンソールに表示されるプログラムです。

HTML
<form id="form">
  <textarea placeholder="入力して文字数を確認しましょう"></textarea>
</form>
JavaScript
'use strict';
{
  const text = document.querySelector('textarea');

    text.addEventListener('input', () => {
      console.log(`${text.value.length}の文字数を入力しています`);
    });

}

フォームを送信する

formタグにはsubmitというイベントがあります。
formで送信するとページが遷移されて結果が消えてしまうため、ページ遷移を無効化しまして結果を確認します。

ページ遷移を無効化するには、eventオブジェクトを渡して、それを無効化するとかけばOKです。
規定の操作を無効化できるのが、preventDefault()です。

clickイベント使うとクリックしないと送信できませんが、formタグを使うとエンターキーでフォームを送信できるというメリットがあります。

では、formのsubmitイベントが実行されたときに「submitされました。」とコンソールに出るプログラムで確認してみてみましょう。(実際には何も送信されません。)

HTML
<form id="form">
  <input type="text" placeholder="入力して送信してみましょう"></input>
  <button>submitをチェックする</button>
</form>
JavaScript
'use strict';
{
  const form = document.querySelector('form');

    form.addEventListener('submit', e => {
      e.preventDefault();
      console.log('submitされました。');
    });

}