HTMLに味付けするためのバニラJS tips集
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;
要素の生成、追加
let ul = document.querySelector('#list');
let li = document.createElement('li');
li.textContent = 'new item';
ul.appendChild(li);
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()
で配列化すれば使える
-
- NodeList(
-
break
で処理を止めることはできない - Array.prototypeに定義されているメソッド
- for、for…in、for…ofのようなループ構文ではなく、ただのメソッド
const strings = ['aaa', 'bbb', 'ccc'];
strings.forEach(s => {
console.log(s);
});
const strings = ['aaa', 'bbb', 'ccc'];
strings.forEach( (s, index) => {
console.log(index + s);
});
for...of
- NodeListもHTMLCollectionもループできる
-
break
で処理を止めることができる
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 a = 0;
a = 1; // error
let b = 0;
b = 1; // ok
const a = [0, 1, 2];
a[0] = 3; // ok
var
を使わない理由
- スコープがゆるい
- if文などの中で定義した変数が、if文の外からも参照できてしまう
- ホイスティング(変数の巻き上げ)が発生する
- 変数を定義する前にその変数を参照しようとすると、普通はエラーが発生するけど、varを使った場合はエラーにならず
undefined
が返される
- 変数を定義する前にその変数を参照しようとすると、普通はエラーが発生するけど、varを使った場合はエラーにならず
何も書かずに定義しない理由
const
もlet
もvar
も使わずに
a = 0
のように書くこともできますが、こうするとスコープのないグローバルな変数になってしまうので、よほどのことがない限り使わない方が良さそうです。
ブラウザのサポート状況について
IEが死んだので、ES2015(ES6)の文法は全部普通に使えます。
let
もconst
もアロー関数もスプレッド構文も分割代入もテンプレートリテラルも使えます。
その他、メソッド等のブラウザの対応状況が知りたい場合は、以下のサイトで確認できます。
データ型の確認
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つの解決策があります。
-
opasity
(0⇄1)とvisiblity
(hidden⇄visible)を使う。これでいけることが多いけど、非表示の時も高さが確保されちゃうので、アコーディオンなどには不向き。 -
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';
参考
Discussion