1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-30

はじめに

前回に引き続き、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

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

HTMLは階層構造になっているのが特徴。
DOMによって解析されたHTMLは階層構造のあるデータとなる。
これをDOMツリードキュメントツリーと呼ぶ。
DOMツリーはデータとして階層構造になっていることで、階層構造を扱うためのアルゴリズムを使い操作できる。
image.png

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

ノード

HTML1つ1つのタグが、DOMツリーの中ではノードと呼ぶ。
image.png

DOMツリーの情報を見る

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

ノードの取得

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

document.getElementById("id名");

documentは、開いているwebページのDOMツリーが入っているオブジェクト。
documentに対していくつかのメソッドを利用することでDOMツリーに含まれる要素を抽出することができる。
.getElementById("id名");はDOMツリーから特定の要素を取得するためのメソッドの1つ、引数に渡した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

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

コンソールでボタン要素を取得する

コンソールを開く(command + option + j)
コンソールで以下のコードを実行する。

document.querySelector("button");

戻り値として<button id= "Button">ボタン</button>という HTMLタグのようなものが得られる。これがノード。
Image from Gyazo

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

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

ページの読み込みをするwibdow.onloadを使う

ページの読み込みは以下の2つの記述方法がある

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

コードを見直す

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

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の第2引数部分を書き換える。

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

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

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

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

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

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

だいぶスッキリした。

ここまでのまとめ

image.png

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

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

DOMの置き換え

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

innerHTML

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

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

~ 省略 ~

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

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

以下のように、ボタン2をクリックしたらテキストが置換されるか確かめる。
Image from Gyazo

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

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

まとめ

最終的に、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");

// ボタン2をクリックしたらテキストが置換される
  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

使用する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

コンソール上でクラスの移動が確認できる。
ここで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)、第3引数(arrey)を省略した場合、配列の各要素(value)だけ取り出せる。

indexOf()

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

#今回はここまで!

次回に続く

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?