今更ながらJavaScriptを勉強し直す 3. 画面上の表示を変える

# はじめに
[前回](https://qiita.com/myktmht/items/53a45b5e8015152ebcca)に引き続き、JavaScript(以降JS)の復習をしていきます。
今回は、JSを用いて、画面上の表示を変える方法をやっていきます。

今回用いるindex.html, style.cssの内容は以下の通り

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>JS Practice</title>
    <link rel="stylesheet" href="style.css" charset="utf-8">
    <script src="./main.js"></script>
  </head>
  <body>
    <h1 id="title">タイトル</h1>
    <img src="himawari.jpg" width="360"/>
    <h2 class="sub-title">サブタイトル</h2>
    <p>
      コンテンツコンテンツコンテンツコンテンツ<br>
      コンテンツコンテンツコンテンツコンテンツ<br>
      コンテンツコンテンツコンテンツコンテンツ<br>
      コンテンツコンテンツコンテンツコンテンツ<br>
    </p>
    <div class="blue">青青青青青</div>
    <button id="Button">ボタン</button>
    <button id="Button2">ボタン2</button>
    <button id="Button3">クラス追加</button>
    <button id="Button4">クラス削除</button></br>
 
    <footer>
        <a href="#" class="next">次へ</a>
    </footer>
  </body>
</html>
```

```style.css
.red {
  background-color: red;
}

.blue {
  background-color: blue;
}
```

[![Image from Gyazo](https://i.gyazo.com/99a4cbc13bbdeccb9369a180e34de436.png)](https://gyazo.com/99a4cbc13bbdeccb9369a180e34de436)

# JSで簡単な機能を実装する
それでは最初に**ボタンを押したらコンソール上に「Hello world」と出る**簡単な機能を実装する。

## 「ボタン」をJSで扱えるようにする。
ボタンはHTMLの要素、HTMLの要素をJS上で取得し操作するためにDOMという仕組みを利用する。

### DOM
DOMとは**Document Object Model(ドキュメントオブジェクトモデル)**の略。
HTMLを解析し、データを作成する仕組みのこと。

**〜HTMLやCSSがWEBページとして閲覧するまでの流れ〜**

**①HTMLを解析、DOMに変換**
HTMLで書かれたファイルは結局ただの文字情報なので、そのまま表示しても意味がない。

**②CSS,JSを読み込み、見た目を描画**
そこで、HTMLを解析し、CSSやJSによる装飾を行ってから画面に映す。

**③ユーザーがページを閲覧**
これを行っているのが、Google ChromeやSafariというブラウザである。

以上の手順を踏んでいる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414101/f39ede2a-6cc3-afee-24de-2ad9ed6846be.png)


HTMLは階層構造になっているのが特徴。
DOMによって解析されたHTMLは階層構造のあるデータとなる。
これを**DOMツリー**や**ドキュメントツリー**と呼ぶ。
DOMツリーはデータとして階層構造になっていることで、階層構造を扱うためのアルゴリズムを使い操作できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414101/1fa96926-3613-4086-2a02-1f9b7f04c6a6.png)

JSを使うと、DOMツリーを操作することができる。
HTMLの要素名や「id,class」といった属性の情報を元に、DOMツリーの一部を取得し、
CSSを変更したり、要素を増やしたり、消したりできる。
すると、これがブラウザに反映され、描画も変わる。
DOMツリーの一部のことを、**ノードオブジェクト**と呼ぶ

### ノード
HTML1つ1つのタグが、DOMツリーの中ではノードと呼ぶ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414101/4a392d6e-a824-09db-d782-ca007a40b8bd.png)

### DOMツリーの情報を見る
index.htmlをブラウザで開いて、検証ツールを見る。
右クリックで「検証」を選択、もしくは「command + option + i」で立ち上げられる。
エレメントツリーを見ると、DOMツリーの情報を確認することができる。
[![Image from Gyazo](https://i.gyazo.com/c5ec9048d115fd8c185cb098a363d924.png)](https://gyazo.com/c5ec9048d115fd8c185cb098a363d924)
エレメントツリーを確認したら、今度はボタンにあたるノードを取得する

### ノードの取得
JSを使ってHTML要素のid名やクラス名を指定することで。マッチするノードを取得できる。

#### document.getElementById("id名");
`document`は、開いているwebページのDOMツリーが入っているオブジェクト。
documentに対していくつかのメソッドを利用することでDOMツリーに含まれる要素を抽出することができる。
`.getElementById("id名");`はDOMツリーから特定の要素を取得するためのメソッドの１つ、引数に渡したidを持つ要素を取得する。
以下のように書くことで、マッチするノードを取得することができる。

```
document.getElementById("id名");
```

#### document.getElementsByClassName("class名");
classを指定して取得する際はこれを利用する。
気をつけることは、`getElements`と複数形になっていること。
id名はhtml上に必ず1つしかないのに対し、class名を指定する
getElementsByClassName("class名");の場合は、同じclassを持つ要素を全て取得することが可能。
以下のように書くことで、クラスメイトマッチするデータを取得することができる。

```
document.getElementsByClassName("class名");
```

#### document.querySelector("セレクタ名");
セレクタ名とは、CSSでスタイルを適用するために指定している要素。
セレクタ名を指定してDOMを取得する場合、querySelectorメソッドを使用する。
HTML上から、引数で指定したセレクタに合致するもののうち、一番最初に見つかった要素1つを取得する。

```
document.querySelector("セレクタ名");
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414101/a862f92f-4a60-5158-79ea-19df73337802.png)

どれを使用しても良いが、今回はquerySelectorメソッドを使用する。

## コンソールでボタン要素を取得する
コンソールを開く(command + option + j)
コンソールで以下のコードを実行する。

```
document.querySelector("button");
```

戻り値として`<button id= "Button">ボタン</button>`という HTMLタグのようなものが得られる。これがノード。
[![Image from Gyazo](https://i.gyazo.com/6c995cc9562de8334d75486ee773e713.png)](https://gyazo.com/6c995cc9562de8334d75486ee773e713)

querySelectorメソッドでは、複雑なセレクタも指定できる。

```
// idがButton2のbuttonタグ要素
document.querySelector("button#Button2");

// footerタグのクラスがnextのaタグ要素
document.querySelector("footer a.next");
```

これでquerySelectorメソッドを使用して、ボタンをJSで取得することができた。



##ここまでのまとめ
DOMの取得とは、`querySelectorメソッドなどを使用してHTML要素をノードとして取得すること。
`ノードを取得してからJSで操作することが多いので、この流れを押さえておく。

# 「ボタンをクリックしたこと」を取得する
取得のきっかけとなる操作についてJSでどのように検知するのか、JSの**イベント**と呼ばれる概念を使う。

## イベント
JSにおけるイベントとは、HTMLの要素に対して行われた処理要求のこと。
例えば、「ユーザーがブラウザ上のボタンをクリックした」「テキストフィールドでキー入力をした」「要素の上にマウスカーソルを乗せる」などがいずれもイベントである。
また、イベントを起こすのはユーザーだけではなく。「ドキュメントの読み込みが終わった」などブラウザが発生させるものもある。

## イベント駆動
JSはイベント駆動という概念に基づいて設計されている。
これは、「イベント」が発生したら、それをきっかけにコードが実行される仕組みのこと。
JSは元々ブラウザに搭載するための言語として開発されており、ユーザーが行う様々な操作に対応できるイベント駆動の仕組みが適しているため。
イベントを取得するためには、先に取得したノードに対して処理を書く。

## addEventListener
`addEventListener`メソッドはノードオブジェクトのメソッドで以下のように実行する。

```
(ノードオブジェクト).addEventListener("イベント名", 関数);
```

上記のような記述により、この記述の読み込み以降で「ノードオブジェクト」に「イベント」が起きた時、「関数」を実行するようになる。
1つのイベントと1つの関数を紐づける仕組みのことを**イベントリスナ**と呼ぶ。

1つのイベントに複数に関数を紐づける場合は、関数の数だけイベントリスナが存在する。
`addEventListener`メソッドはあるノードオブジェクトに対して、イベントリスナを追加するメソッド。

# ボタンを押したら、コンソールに「Hello world」と表示されるようにする

main.jsに以下のコードを追記する。

```main.js
let btn = document.querySelector("button");
// ボタンをノードオブジェクトとして取得し、変数btnに代入する

function printHello() {
  console.log("Hello world");
}
// printHello関数を定義

btn.addEventListener("click", printHello);
// ボタンのノードオブジェクトであるbtnに対して、
// clickイベントとprintHello関数を紐付ける仕組みであるイベントリスナを追加する
```

これでコンソールに「Hello world」と出力されるはず。
しかし、ボタンを押しても反応せず、コンソールを開くと以下のエラーが出ている。

```
cannot read property 'addEventListener' of null
```

今回コードを記述したmain.jsはindex.htmlのheadタグ内に記述して読み込んでいる。

ブラウザは上から順に実行するので、このJSコードを読み込むとき、**まだ、htmlファイルのheadタグ内までしか読み込まれておらず、bodyタグ内にあるbuttonタグは読み込まれていない。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414101/85f5ac63-5895-8660-50e3-f53d72e8b591.png)

そのため、`querySelector`メソッドによりボタンのノードオブジェクトは取得できておらず、**変数btnの中身はnull(何もない)**となっていた。
nullのプロパティとして`querySelector`メソッドが実行されるため、コンソールで「nullのaddEventListenerは読み込めない」エラーが発生した。
これを解決するために、上記の一連の流れを「ページの読み込みが終わったら」main.jsの中身を実行する。
## ページの読み込みをするwibdow.onloadを使う
ページの読み込みは以下の２つの記述方法がある

```
window.onload = function() {処理};
```

```
window.addEventListener('load', function () {処理});
```

「ページの読み込みが終わる」イベントは、windowオブジェクトのloadイベントに対応する。
そこで、windowオブジェクトのloadイベントに対応する関数として上記の一連の処理を定義すれば良いと考えられる。
なお、windowオブジェクトは、元から用意されているブラウザの情報を持つオブジェクト。

### ページの読み込みが行えるようにする

```main.js
unction printHelloWithButton() {
  let btn = document.querySelector("button");
  // ボタンをノードオブジェクトとして取得し、変数btnに代入する。

  function printHello() {
  console.log("Hello world");
  }
  // printHello関数を定義
  // 関数内で定義された関数は、関数の中でしか呼び出せない性質があるだけで、
  // 通常の関数同様に呼び出せる。 

  btn.addEventListener("click", printHello);
  // ボタンのノードオブジェクトであるbtnに対して、
  // clickイベントとprintHello関数を紐づける仕組みであるイベントリスナを追加する
}
// 一連の処理をまとめた関数を作る
window.addEventListener("load", printHelloWithButton);
```

これにより、windowオブジェクトのloadイベントが起きたら、つまり、ページの読み込みが終わったら、`printHelloWithButton`関数が実行される。
この時、ページは読み込まれているので、DOMオブジェクトとして、buttonタグを取得することができる、イベントリスナを正常に追加することができる。

実際にトップページにアクセスし、ボタンをクリックすると、コンソール上に「Hello world」の出力がされる。

[![Image from Gyazo](https://i.gyazo.com/61427a82fede452ca83c848f07312928.gif)](https://gyazo.com/61427a82fede452ca83c848f07312928)

## コードを見直す
上記のコードを見直してみる。
まず、`console.log`1行のために関数を定義しているのは冗長であると考えられる。
わざわざ関数として定義しているのは`addEventListener`メソッドの第２関数として関数を指定しなくてはいけないため。
そこで、`addEventListener`メソッドの第２関数内で関数を定義することを考える。

```
function func() {}
  // 何もしないfunc関数
btn,addEventListener("click", func);

↓　このように書き直せる
btn.addEventListener("click", function func() {});
```

`addEventListener`の第2引数部分に関数の定義部分を移動しただけ。
また、先ほどは関数を呼び出すために関数名をつけていたが、以下のように名前（上の例で言えばfunc）を省略することができる。
名前のない関数のことを無名関数と呼ぶ。

```
btn.addEventListener("click", function() {});
```

上の例では関数の中身は何もないが、処理がある場合（ほとんどの場合）は一般に改行を用いて以下のように書く。

```
btn.addEventListener("click", function() {
  // 処理
});
```

### コードが短くなるように、中身を書き換える
まず、`window.addEventListener`の第２引数部分を書き換える。

```main.js
window.addEventListener("load", function() {
  let btn = document.querySelector("button");

  function printHello() {
    console.log("Hello world");
  }

  btn.addEventListener("click", printHello);
});
```

次に、`btn.addEventListener`の第２引数部分を書き換える。

```main.js
window.addEventListener("load", function() {
  let btn = document.querySelector("button");

  btn.addEventListener("click", function() {
    console.log("Hello world");
  });
});
```

だいぶスッキリした。

## ここまでのまとめ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/414101/a8111231-0f21-9d8b-bebe-ffdb5256fc0c.png)

**① DOMツリーからノードを取得**
**② JSでやりたい処理を書く**
**③ イベント発火でHTML側で動かす**

このイメージをつかむことができれば、今までやってきたことを応用してJS側でいろんな処理を書いて、画面に動きを持たせる実装を実現できる。

## DOMの置き換え
ボタン２を押したら、テキストが置換できるようにする。

### innerHTML
`innerHTML`を使用するとHTML要素の中身を書き換えられる。

```main.js
window.addEventListener("load", function() {

~ 省略 ~

// テキストの要素を取得し、変数で定義   
  let btn2 = document.querySelector("#Button2");
  let changeText = document.querySelector("p");

// ボタン２をクリックしたらテキストが置換される
  btn2.addEventListener("click", function() {
    changeText.innerHTML = '変更しました';
  });
});
```

以下のように、ボタン２をクリックしたらテキストが置換されるか確かめる。
[![Image from Gyazo](https://i.gyazo.com/653f31f7607c77f996e374636f7192ab.gif)](https://gyazo.com/653f31f7607c77f996e374636f7192ab)

## DOMの追加
次は、「ボタンをクリックするとクラス要素を追加される機能」を実装する。
今回はCSSの背景色が赤のクラスの`redクラス`が追加。さらに、`console.log`を使用してデバックをする。

### classList.add
`classList.add`を使用すると、HTML要素を追加できる。

```main.js
window.addEventListener("load", function() {

~ 省略 ~

// Button3を取得して、変数で確認
  let btn3 = document.querySelector("#Button3");

// クラス追加を押したらredクラスが追加される
// コンソールにデバックを表示するためconsole.logを追記
  btn3.addEventListener("click", function () {
    changeText.classList.add("red");
    console.log(changeText.classList);
  });
});
```

これで、ボタンを押すと、redクラスが追加。
押した際にコンソール上に`DOMTokenList ["red", value: "red"]`と表示されればデバックも成功。
`console.log`はJSがちゃんと動いているかどうか、デバックする際によく使われるので、押さえておこう。

[![Image from Gyazo](https://i.gyazo.com/17f0574f814c2864623be00e2fc14081.gif)](https://gyazo.com/17f0574f814c2864623be00e2fc14081)

## DOMの削除
すでにあるクラスを削除する実装をする。
「クラス削除ボタン」を押すと、青背景に設定している`blueクラス`が削除されるようになる。

### classList.remove
`classList.remove`を使用するとHTML要素を削除できる。

```main.js
window.addEventListener("load", function() {

~ 省略 ~

// Button4を取得して、変数で定義
  let btn4 = document.querySelector("#Button4");

// div要素を取得して、変数で定義
  let obj = document.querySelector("div");

// クラス削除を押したらblueクラスが削除される
  btn4.addEventListener("click", function() {
    obj.classList.remove("blue");
  });
});
```
blueクラスが削除され、青背景がなくなるのを確認する。

[![Image from Gyazo](https://i.gyazo.com/b2a9b38e0e1f5a6d2e1522b49b348cf5.gif)](https://gyazo.com/b2a9b38e0e1f5a6d2e1522b49b348cf5)


# まとめ
最終的に、main.jsの中身は以下のようになる。

```main.js
// 一連の処理をまとめた関数を作る
window.addEventListener("load", function() {

// ボタンをノードオブジェクトとして取得し、変数btnに代入する。
  let btn = document.querySelector("button");

// ボタンのノードオブジェクトであるbtnに対して、
// clickイベントと関数を紐づける仕組みであるイベントリスナを追加する
  btn.addEventListener("click", function() {
    console.log("Hello world");
  });

// テキストの要素を取得し、変数で定義   
  let btn2 = document.querySelector("#Button2");
  let changeText = document.querySelector("p");

// ボタン２をクリックしたらテキストが置換される
  btn2.addEventListener("click", function() {
    changeText.innerHTML = '変更しました';
  });

// Button3を取得して、変数で確認
  let btn3 = document.querySelector("#Button3");

// クラス追加を押したらredクラスが追加される
// コンソールにデバックを表示するためconsole.logを追記
  btn3.addEventListener("click", function () {
    changeText.classList.add("red");
    console.log(changeText.classList);
  });

// Button4を取得して、変数で定義
  let btn4 = document.querySelector("#Button4");

// div要素を取得して、変数で定義
  let obj = document.querySelector("div");

// クラス削除を押したらblueクラスが削除される
  btn4.addEventListener("click", function() {
    obj.classList.remove("blue");
  });
});
```

# 応用編
ここで軽い応用問題があったのでそれを解いていく
問題の内容としては、ここまで学んだ知識を利用するものである。

出来上がると、以下のようにタブの切り替えができるようになる。
[![Image from Gyazo](https://i.gyazo.com/a50209c0235552ce0cae380b3abb7334.gif)](https://gyazo.com/a50209c0235552ce0cae380b3abb7334)

使用するindex.htmlのbodyタグを確認

```index.html
<body>
  <div class="nav-bar">
    サービス一覧
  </div>
  <div class="container">
    <ul class="menu">
      <li><a href="#" id="about" class="menu_item active">Rails</a></li>
      <li><a href="#" id="service" class="menu_item">Javascript</a></li>
      <li><a href="#" id="contact" class="menu_item">Ruby</a></li>
    </ul>
    <ul class="contents">
      <li class="content show">
        about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about about
      </li>
      <li class="content">
        service service service service service service service service service service service service service service service service service service service service service service service service service service
      </li>
      <li class="content">
        contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact contact
      </li>
    </ul>
  </div>
</body>
```

`activeクラス`と`showクラス`が付いているものが、CSSで`display:none;`で表示できないようになっている。
タブメニューをクリックした時に、対応するクラスに`activeクラス`と`showクラス`を追加することで実装することができる。

[![Image from Gyazo](https://i.gyazo.com/be1fc4a49d4dad9c48299718720edd44.gif)](https://gyazo.com/be1fc4a49d4dad9c48299718720edd44)

コンソール上でクラスの移動が確認できる。
ここでmain.jsの中身を穴埋め形式で回答し、完成したコードが以下になる。

```main.js
window.addEventListener("load", function() {
  // タブのDOMを取得し、変数で定義
  let tabs = document.getElementsByClassName("menu_item");
  // tabsを配列に変換する
  tabsAry = Array.prototype.slice.call(tabs);

  // クラスの切り替えをtabSwitch関数で定義
  function tabSwitch() {
    // 全てのactiveクラスのうち、最初の要素を削除（"[0]は、最初の要素の意味"）
    document.getElementsByClassName("active")[0].classList.remove("active");

    // クリックしたタブにactiveクラスを追加
    this.classList.add("active");

    // コンテンツの全てのshowクラスのうち、最初の要素を削除
    document.getElementsByClassName("show")[0].classList.remove("show");

    // 何番目の要素がクリックされたかを、配列tabsから要素番号を取得
    const index = tabsAry.indexOf(this);

    // クリックしたcontentクラスにshowクラスを追加する
    document.getElementsByClassName("content")[index].classList.add("show");
  }

  // タブメニューの中でクリックイベントが発生した場所を探し、下で定義したtabSwitch関数を呼び出す
  tabsAry.forEach(function(value) {
    value.addEventListener("click", tabSwitch);
  });
});
```

先程までの内容で出てきていないものに関して触れておく。

### this
**関数(tabSwitch)の中で使う**と、イベントの発生元となった要素を取得できる。
thisの使い方については、次回以降触れていく。

### Arrey.prototype.slice.call()
引数に取ったオブジェクトを配列に変換する。

### forEach()
配列に対してよく使われる繰り返し処理。

```
配列.forEach(コールバック関数)
```

コールバック関数とは、配列の各要素に対して行う処理のこと。

```
配列.foreach(function (value, index, arrey){
  // 配列のデータに対しての処理、valueは値、indexは要素番号、arreyは処理している配列のこと
})
```
第2引数(index)、第３引数(arrey)を省略した場合、配列の各要素(value)だけ取り出せる。

### indexOf()
配列に対してだけ使い、DOMを引数に取って一致した要素番号を戻す。


#今回はここまで！

[次回に](https://qiita.com/myktmht/items/a6af3325b0785310d074)続く