🍦

HTMLに味付けするためのバニラJS tips集

2022/09/19に公開

ReactやVueを使わずに、素のJavaScriptでDOM操作する時のやつです。

何度も同じようなことをググって時間を溶かしていたので、よく使うやつをまとめてみました。

要素の取得

// 最初に見つかった要素1つを取得
document.querySelector("#hoge-id"); //[object HTMLElement]
document.getElementById("hoge-id"); //[object HTMLElement]

//条件に合う要素を全て取得
document.querySelectorAll(".hoge-class"); // [object NodeList]
document.getElementsByClassName("hoge-class"); // [object HTMLCollection]

Array.from(document.querySelectorAll(".hoge-class")); // [object Array]

属性の操作

idでもclassでもhrefでもtargetでもforでもtitleでも、大体なんでも操作できます。

// 取得
element.getAttribute('href');

// 書き換え(追加)
element.setAttribute('href', 'https://google.com');

// 削除
element.removeAttribute('href');

classの操作

// 書き換え
element.className = 'hoge';

// 追加
element.classList.add('hoge');

// 削除
element.classList.remove('hoge');

// 付け外し
element.classList.toggle('hoge');

// クラスの有無を確認
element.classList.contains('hoge');

テキストの操作

// 取得
element.textContent;

// 書き換え
element.textContent = 'Hello World';

// 追記
element.textContent += '追加テキスト';

// 削除
element.textContent = '';

隣接要素の取得

// 親要素
element.parentNode;

// 子要素
element.children; // [object HTMLCollection]

// 同階層の1つ前の要素
element.previousElementSibling;

// 同階層の1つ後の要素
element.nextElementSibling;

要素の生成、追加

appendChild
let ul = document.querySelector('#list');
let li = document.createElement('li');
li.textContent = 'new item';
ul.appendChild(li);
innerHTML
let div = document.querySelector('#container');
div.innerHTML = `
  <ul id="list">
    <li>item1</li>
    <li>item2</li>
    <li>item3</li>
  </ul>
`;

ループ処理

forEach

  • 一番よく使う
  • 配列etcをループできる
    • NodeList(querySelectorAllの返り値)はループできる
    • HTMLCollection(getElementsByClassName.childrenの返り値)はループできない
      • Array.from()で配列化すれば使える
  • breakで処理を止めることはできない
  • Array.prototypeに定義されているメソッド
    • for、for…in、for…ofのようなループ構文ではなく、ただのメソッド
forEach
const strings = ['aaa', 'bbb', 'ccc'];
strings.forEach(s => {
    console.log(s);
});
forEach(indexがほしい時)
const strings = ['aaa', 'bbb', 'ccc'];
strings.forEach( (s, index) => {
    console.log(index + s);
});

for...of

  • NodeListもHTMLCollectionもループできる
  • breakで処理を止めることができる
for of
const strings = ['aaa', 'bbb', 'ccc'];
for(let s of strings) {
    console.log(s);
}

data属性の利用

<button data-hoge-hoge="Hello World">ボタン</button>
const btn = document.querySelector('button');
console.log(btn.dataset.hogeHoge);

addEventListener

addEventListenerを使うことで、特定のDOM要素に特定の動き(clickとか)があったときに、処理を実行するように設定できます。

基本

<button>ボタン</button>
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
  console.log('Hello World');
});

複数の要素に設定する場合

<ul>
  <li>aaa</li>
  <li>bbb</li>
  <li>ccc</li>
</ul>
const lists = document.querySelectorAll('li');
lists.forEach(l => {
  l.addEventListener("click", () => {
    console.log('Hello World');
  });
});

eventを利用する場合

第一引数にeventを受け取ることができます。
またe.targetでイベントが発生した要素(≒clickされた要素)を参照することができます。

<button>ボタン</button>
const btn = document.querySelector('button');
btn.addEventListener('click', e => {
  console.log(e.target.textContent);
});

即時関数

バニラJSでHTMLを操作するとき、特に理由がない限り、即時関数を使った方が無難です。
以下、例を使って説明します。

即時関数を使わない場合1

<button onclick="logTextContent(event)">ボタン</button>
function logTextContent(e) {
  console.log(e.target.textContent);
};

この場合logTextContentという関数名が、他の関数と被らないように気をつける必要があります。
また「この関数は、この場所でだけ使う(他の場所で使い回さない)」ということが明示できません。

即時関数を使わない場合2

<button>ボタン</button>
const btn = document.querySelector('button');
btn.addEventListener('click', e => {
  console.log(e.target.textContent);
});

この場合btnという変数名が、他の変数と被らないように気をつける必要があります。

即時関数を使う場合

<button>ボタン</button>
(() => {
  const btn = document.querySelector('button');
  btn.addEventListener('click', e => {
    console.log(e.target.textContent);
  });
})();

即時関数を使うと、関数名も変数名もスコープが限定されるので、予期せぬ事故を減らすことができます。

アロー関数の省略記法

  • 引数が1つの場合、引数を囲む()を省略できる
  • 処理の中身が1行の場合、処理を囲む{}を省略できる
省略しない場合
const btn = document.querySelector('button');
btn.addEventListener('click', (e) => {
  console.log(e.target.textContent);
});
省略する場合
const btn = document.querySelector('button');
btn.addEventListener('click', e => console.log(e.target.textContent));

文末のセミコロンの省略について

文末のセミコロン;は、多くの場合省略しても問題なく動きます。
でも「即時関数」と「配列リテラル」の定義の直前だけは、セミコロンがないとエラーが発生します。

個人的には、毎回必ずセミコロンをつけるようにした方が考えることが減るので、楽だと思っています。

変数の定義方法について

結論

基本的にconstを使って、再代入が必要な時だけletを使います。

constを優先する理由

constを使うことで「この変数は再代入されないよ」ということを明示することができます。
またletをみたときに「再代入されるんだな」と予想を立てることができるようになります。

constは再代入できない
const a = 0;
a = 1; // error

let b = 0;
b = 1; // ok
constでもobjectの中身の編集はできる
const a = [0, 1, 2];
a[0] = 3; // ok

varを使わない理由

  • スコープがゆるい
    • if文などの中で定義した変数が、if文の外からも参照できてしまう
  • ホイスティング(変数の巻き上げ)が発生する
    • 変数を定義する前にその変数を参照しようとすると、普通はエラーが発生するけど、varを使った場合はエラーにならずundefinedが返される

何も書かずに定義しない理由

constletvarも使わずに

a = 0

のように書くこともできますが、こうするとスコープのないグローバルな変数になってしまうので、よほどのことがない限り使わない方が良さそうです。

ブラウザのサポート状況について

IEが死んだので、ES2015(ES6)の文法は全部普通に使えます。
letconstもアロー関数もスプレッド構文も分割代入もテンプレートリテラルも使えます。

その他、メソッド等のブラウザの対応状況が知りたい場合は、以下のサイトで確認できます。
https://caniuse.com/

データ型の確認

type_of()を使って、5つのプリミティブ型を判定できます。

typeof('str');     // 'string'
typeof(1);         // 'number'
typeof(true);      // 'boolean'
typeof(null);      // 'object' (ECMAScript標準規格によれば独自の型のはず...)
typeof(undefined); // 'undefined'

ただ何でもかんでもobjectになるので、詳細が知りたい場合はObject.prototype.toString.call()を使います。

Object.prototype.toString.call({});                // [object Object]  
Object.prototype.toString.call([]);                // [object Array]
Object.prototype.toString.call(function() {});    // [object Function]
Object.prototype.toString.call(new Error());       // [object Error]
Object.prototype.toString.call(new Date());        // [object Date]
Object.prototype.toString.call(JSON);              // [object JSON]
Object.prototype.toString.call(Math);              // [object Math]
Object.prototype.toString.call(new RegExp());      // [object RegExp]
Object.prototype.toString.call(new String('str')); // [object String]
Object.prototype.toString.call(new Number(1));     // [object Number]
Object.prototype.toString.call(new Boolean(true)); // [object Boolean]

関連するCSSのTIPs

displayにはtransitionが効かない

表示/非表示の切り替えをフワッとさせたい時、displayの値をnone⇄blockで切り替えて対応すると、transitionが効かないのでうまくいきません。

この問題には、以下の2つの解決策があります。

  1. opasity(0⇄1)とvisiblity(hidden⇄visible)を使う。これでいけることが多いけど、非表示の時も高さが確保されちゃうので、アコーディオンなどには不向き。
  2. max-height(0⇄element.scrollHeight)を使う。これなら非表示の時に高さが確保されない。

その他よく使うやつ

<a href="#" onclick="return alert('アラート');">Alert</a>

<a href="https://google.com" onclick="return confirm('本当に移動しますか?');">Confirm</a>

<a href="javascript:history.back();">戻る</a>
element.style.zIndex = '10';

参考

https://web-begginer-log.com/js_for/

https://zenn.dev/nagan/scraps/b48f45f508e54e

https://zenn.dev/ankouh/books/javascript-dom/viewer/b412c2

https://qiita.com/katsukii/items/cfe9fd968ba0db603b1e

https://nishinatoshiharu.com/immediate-function-overview/

https://qiita.com/cheez921/items/7b57835cb76e70dd0fc4

Discussion