【脱jQuery】よく使うJavaScriptの書き方 - 要素の取得、ループ処理、クラス名の追加・削除など
今回はjQueryではなく、ネイティブのJavaScrip(Vanilla.jsとも呼ぶ)で、個人的に良く使う書き方についてまとめてみました。今後も少しずつ追加していきます。
【脱jQuery】よく使うJavaScriptの書き方
書き方については、なるべくIE11対応で書きますが確実ではありません。Babelなどでトランスパイルすることをおすすめします。
要素の取得
// HTMLElement | 存在しない場合はnullを返す
//IDを持つ要素を取得
document.getElementById('hoge');
//HTMLCollection | 存在しない場合は、空のHTMLCollectionを返す
//タグで取得
document.getElementsByTagName('p');
//HTMLCollection | 存在しない場合は、空のHTMLCollectionを返す
//クラス名で取得
document.getElementsByClassName('hoge');
//HTMLElement
//マッチした最初の要素のみ取得 | 存在しない場合はnullを返す
document.querySelector('.hoge');
//NodeList
//マッチした要素をすべて取得 | 存在しない場合は空のNodeListを返す
document.querySelectorAll('.hoge');
//NodeList | 存在しない場合は空のNodeListを返す
//target="_black"が設定されていないa要素を取得
document.querySelectorAll('a:not([target="_blank"])');
//NodeList | 存在しない場合は空のNodeListを返す
//target="_black"が設定されているa要素を取得
document.querySelectorAll('a[target="_blank"]');
要素を取得するスピードは単一の要素を取得するgetElementByIdやquerySelectorが早い。要素があるかないかの判定に便利です。
querySelectorAllは扱いやすいですが、取得スピードが遅いので複数の要素を取得する場合はgetElementsByClassNameやgetElementsByTagNameを使います。
/*---- 取得スピードの比較 ----*/
document.querySelectorAll('div');
document.getElementsByTagName('div'); //こちらのほうが早い
document.querySelectorAll('.hoge');
document.getElementsByClassName('hoge'); //こちらのほうが早い
親要素、子要素、兄弟要素の取得
const elem = document.getElementById('elem'); //HTMLElement
//親要素の取得
const parent = elem.parentNode;
const parent2 = elem.parentElement;
//子要素の取得
const child = elem.children;
//兄弟要素の取得
const brother = elem.nextElementSibling;
HTMLElementであれば上記の方法で親要素や子要素を取得できます。ただし、HTMLCollectionやNodeListの場合はループで要素を特定してから、取得したい親要素や子要素を指定する必要があります。
const elem = document.getElementsByClassName("elem"); //HTMLCollection
for (let i = 0; i < elem.length; i++) {
const parent = elem[i].parentElement;
}
内側の要素の書き換え
/*-- id名hogeの内側の要素を取得 --*/
//HTMLElement
const elem1 = document.getElementById('hoge');
elem1.innerHTML = '<p class="ahoaho"></p>'; //内側を書き換え
/*-- headerタグの内側の要素を取得 --*/
//HTMLCollection
const elem2 = document.getElementsByTagName('header');
elem2[0].innerHTML = '<p class="ahoaho"></p>'; //内側を書き換え
/*-- footerタグの内側の要素を取得 --*/
//NodeList
const elem3 = document.querySelectorAll('footer');
elem3[0].innerHTML = '<p class="ahoaho"></p>'; //内側を書き換え
HTMLCollectionやNodeListとして取得したものは、[0]をつけてからinnerHTMLを指定することで内側の要素を書き換えることができます。HTMLElementの場合は[0]はいりません。
テキストの取得、差し替え
const elem = document.getElementById('hoge');
/*-- textContentの場合 --*/
//テキストの取得
const txt1 = elem.textContent;
//テキストの差し替え
elem.textContent = "私は太朗です";
/*-- innerTextの場合 --*/
//テキストの取得
const txt2 = elem.innerText;
//テキストの差し替え
elem.innerText = "私は花子です";
textContentもinnerTextもIE11を含む、どのブラウザでも使えます。どちらを使っても同じ動きになります。明確な違いについては以下の記事を参考にしてみてください。
要素があるか判定
//querySelectorやgetElementByIdなど単一要素を取得するメソッドの場合(nullが返ってくる場合)
const elem = document.querySelector('.hoge');
if(elem !== null){
/* .hogeがnullじゃなれば */
}
//querySelectorAll や getElementsByClassNameなどの複数要素を取得するメソッドを使った場合
const elems = document.getElementsByClassName('hoge');
if(elems.length > 0){
/* .hogeが1つでもあれば */
}
要素があるかないかの判定は、querySelectorやgetElementByIdなど、単一要素を取得するメソッドで対応すると処理が早い。
要素の中に特定のクラス名があるか判定
//クラス名fugaがあればtrue、無ければfalseを返す
document.getElementById('hoge').classList.contains('fuga');
以下は実際に条件分岐して処理をするときに例です。
id名hogeと一緒に、クラス名fugaがあるか無いかで条件分岐しています。
//以下のように条件分岐して使う
function classCheck() {
const elem = document.getElementById('hoge');
if (elem.classList.contains('fuga') == true ) {
/* クラス名fugaがあれば… */
} else {
/* クラス名fugaが無ければ… */
}
}
classCheck(); //関数classCheckを実行
クラスの追加、削除、トグル
//クラスの追加
document.getElementById('hoge').classList.add('fuga');
//クラスの削除
document.getElementById('hoge').classList.remove('fuga');
//クラスのトグル
document.getElementById('hoge').classList.toggle('fuga');
/*-- 余談 --*/
//クラスをつける場合はclassNameもある。
//ただし、すでにあるクラス名や先に付与したクラス名ごと
//置き換わってしまうので注意。
document.getElementById('hoge').className('fuga');
単一の要素ではなく、複数の要素を取得してクラス名を操作する場合は、後ほど紹介するループ処理を使わないとエラーになるので注意しましょう。
属性の取得、設定、削除
//属性の取得
document.getElementById('hoge').getAttribute('href');
//属性の設定
document.getElementById('hoge').setAttribute('href','https://www.willstyle.co.jp');
//属性の削除
document.getElementById('hoge').removeAttribute('href');
特定のクラス名がある要素のhref属性を取得して、別のURLに書き換えることや、img要素のsrc属性を取得して、別の画像パスに変更することもできます。
data属性の設定、取得、更新
const elem = document.querySelector('.elem');
//data属性の設定
elem.dataset.dataName = '値';
//data属性の取得
elem.dataset.dataName;
//data属性の更新
elem.dataset.dataName = '変更したい値';
/*-- 例 --*/
//data-positionがセットされて値に100pxが入る
elem.dataset.position = '100px';
//date-positionにセットされた100pxが取得できる
elem.dataset.position;
//date-positionにセットされた100pxを50pxに変更する
elem.dataset.position = '50px';
data属性は値の設定から変更まで扱いやすいので、DOM操作で多く使われます。
スタイルの取得、設定
//スタイルの取得
document.getElementById('hoge').style.backgroundColor;
//スタイルの設定
document.getElementById('hoge').style.backgroundColor = 'red';
上の例ではcssではbackground-colorと書くプロパティが、backgroundColorとなっています。JavaScript側でプロパティを指定するときは「-」を無くし、その次にある文字を大文字にします。
以下は例です。
- margin-top → marginTop
- max-width → maxWidth
複数のスタイルを指定する書き方もあります。
この場合は通常のcssプロパティで書くことができます。
ただし、cssTextでstyleの指定をした場合、それ以前に設定していたインラインのstyleを上書きする形になるので注意です。
//複数のstyleを設定
document.getElementById('hoge').style.cssText = 'width: 100%; background-color:red;';
それ以外にはsetPropertyもあります。
//setProperty
document.getElementById('hoge').style.setProperty = ('width', '100%');
//!importantを使うときは以下
document.getElementById('hoge').style.setProperty = ('width', '100%', 'important');
setPropertyで一括してstyleを指定する場合は以下です。
styleをオブジェクトにまとめてループさせます。
const styles = {
visibility: 'visible',
overflow: 'hidden',
position: 'fixed',
top: '0px',
left: '0px',
};
Object.keys(styles).forEach((key) => {
document.getElementById('hoge').style.setProperty(key, styles[key]);
});
ループしたときの弱点としてはオブジェクトのkeyにはハイフンが使えないので以下は適用されません。
// オブジェクトキーはハイフンが使えないので適用されない
const styles = {
background-color: 'red',
z-index: '100',
};
Object.keys(styles).forEach((key) => {
document.getElementById('hoge').style.setProperty(key, styles[key]);
});
要素を作る
/*--divを作る--*/
const new_elem = document.createElement("div");
/*--以下は例です--*/
const parent_elem = document.getElementById('parent'); //id名parent要素を取得
const elem = document.createElement("div"); //divを作る
elem.setAttribute("class", "children"); //作ったdivにクラス名childrenをつける
parent_elem.appendChild(elem); //id名parentの中に、クラス名がついたdivを差し込む
要素を削除する
const parent_elem = document.querySelector("#parent");
const child_elem = document.querySelector("#child");
/*-- 子要素を削除 --*/
parent_elem.removeChild(child_elem);
/*-- 自分を削除 --*/
parent_elem.remove();
remove()はIE11では使用できないので、対応させるならpolyfillを読み込んでください。
要素を差し込む
const parent_elem = document.getElementById('parent');
const child_elem = document.getElementById('child');
/*--appendChild--*/
parent_elem.appendChild(child_elem); //子要素の最後に差し込む(1つのみ差し込み可能)
/*--append--*/
parent_elem.append(child_elem); //子要素の最後に差し込む(複数、差し込み可能)
/*--prepend--*/
parent_elem.prepend(child_elem); //子要素の最初に差し込む(複数、差し込み可能)
/*--特定の要素の前や後に埋め込む場合--*/
const target = document.querySelector('#target');
const box1 = document.querySelector('#box1');
const box2 = document.querySelector('#box2');
//targetの前にbox1を差し込む
target.before(box1);
//targetの後にbox2を差し込む
target.after(box2);
appendChildとappendの違いは、要素を複数差し込めるかどうか。appendChildは1つしか差し込めません。
またappendとprepend、それからbeforeとafterはIE11で対応していないので、対応させるならpolyfillを読み込ませる必要があります。
要素の横幅、高さを取得
const hoge = document.getElementById('hoge');
/*--横幅--*/
const fuga_cW = hoge.clientWidth;//paddingを含んだ横幅
const fuga_oW = hoge.offsetWidth;//border、padding、スクロールバーを含んだ横幅
const fuga_sW = hoge.scrollWidth;//paddingを含んだ画面上に表示されていないコンテンツを含む横幅
/*--高さ--*/
const fuga_cH = hoge.clientHeight;//paddingを含んだ高さ
const fuga_oH = hoge.offsetHeight;//border、padding、スクロールバーを含んだ高さ
const fuga_sH = hoge.scrollHeight;//paddingを含んだ画面上に表示されていないコンテンツを含む高さ
ループ処理
const elems = document.getElementsByClassName('.target'); //複数の要素を取得
//1番処理が早い
for (let i = 0; i < elems.length; i++) {
console.log(i, elems[i]);
}
//処理が遅い
const arr_elems = Array.from( elems ); //HTMLCollectionやNodeListは1度、配列化すること
arr_elems.forEach(function( i, val ) {
console.log(i, val);
});
/*-------IE11のforEach対策--------*/
const nodeList = document.querySelectorAll('.target');
const arr = Array.prototype.slice.call(nodeList); //Nodelistを配列化
arr.forEach(function( i, val ) {
console.log(i, val);
});
Array.fromはIE11で対応していないため、別の書き方をする必要があります。
IE11ではNodelistに対してのforEachが対応できていないので、1度配列化してから実行する必要があります。
ループ処理については、以下の記事が参考になります。
複数の要素を取得して、1つ1つの要素に対して何かしら実行したい場合、ループ処理させないとエラーになります。
また、querySelectorAllやgetElementsByClassNameなどで取得できた要素が1つだけの場合でもループ処理は必要になります。
const elem = document.getElementById('hoge'); //一つの要素を取得
const elems = document.querySelectorAll('.fuga'); //複数の要素を取得
elem.classList.add('class-name'); //1つの要素だとエラーにならない
elems.classList.add('class-name'); //複数要素があるとエラー!
/*
* querySelectorAll() などで取得した時は、ちゃんとループさせて処理する
*/
for (let i = 0; i < elems.length; i++) {
elems[i].classList.add('class-name'); //クラスを追加
}
ブラウザ画面の高さ、横幅の取得
//ブラウザの高さを取得
const wH = window.innerHeight;
//ブラウザの横幅を取得(スクロールバーを含む)
const wW = window.innerWidth;
//ブラウザの横幅を取得(スクロールバーを除く)
const wW02 = document.body.clientWidth;
画面いっぱいに画像を表示したいときは、cssでheight: 100vh;と指定すると楽ですが、SP表示のときアドレスバー分、ズレてしまします。
window.innerHeightで取得した高さで設定すればズレが生じません。
実行するタイミング
//速攻で実行 (即時関数)
(function() {
}());
//DOM構造が読み込まれた段階で実行
document.addEventListener('DOMContentLoaded', function() {
});
///DOM構造やCSS、画像など、サイト表示に必要なものをすべて読み込んだタイミングで実行
window.addEventListener('load', function(){
});
//以下の書き方もあるが、最後に代入された関数が上書きされて実行されるため、複数処理がある場合はオススメできない
window.onload = function() {
}
jsファイルの読み込む位置を</body>の直前にして、変数や関数が外のエリアに影響を与えないように、即時関数の中で処理を書いたりできます。
画像がないと正しく動かないものについてはloadイベントを使ってDOM構造やCSS、画像など、サイト表示に必要なものをすべて読み込んだタイミングで実行させます。
その分、loadイベントはDOMContentLoadedより実行スピードは遅くなります。
スクロールイベント
window.addEventListener('scroll', function(){
});
スクロールするごとに処理を実行します。1pxでもスクロールすればイベントが発生するので、多用するとサイトが重くなる原因につながります。
最近ではIntersection Observerを使った実装のほうが処理が軽くなるのでオススメです。
座標を取得
const rect = elem.getBoundingClientRect(); //{x, y, width, height, top, right, bottom, left} のオブジェクト
const top = rect.top; //ページ上からの距離
const left = rect.left; //ページ左からの距離
/*実際に使う場合の例*/
const target = document.getElementById("js-target");
const rect = target.getBoundingClientRect();
const scroll = window.pageYOffset || document.documentElement.scrollTop;//画面上からのスクロール量を計算
const targetPosition = rect.top + scroll; //最初の位置からスクロール分を追加していく
target.style.top = targetPosition + "px";
スクロール量の取得
const scroll = window.pageYOffset || document.documentElement.scrollTop;
配列に変換
//配列に変換
const elems = document.getElementsByClassName('.target');
const arr = Array.from(elems);
//IE対策の書き方
const elems = document.getElementsByClassName(".target"); //ターゲットとなるクラス名js-triggerをすべて格納
const arr = Array.prototype.slice.call(elems, 0);
クリックイベント
const elem = document.querySelector('.button'); //単一の要素を取得
/*-------基本的なクリックイベント---------*/
elem.addEventListener('click', function(){
});
/*---スマホのときはtouchstart、PCではclick---*/
let flag = false;
elem.addEventListener('touchstart', function() {
flag = true;
// 何らかの処理
});
elem.addEventListener('click', function() {
if (flag) {
flag = false;
} else {
// 何らかの処理
}
});
/*---複数の要素に対してクリックイベントを登録する場合----*/
const target = document.querySelectorAll('.target'); //複数の要素を取得
for (let i = 0; i < target.length; i++) {
target[i].addEventListener("click", function (e) {
console.log(e.target);
});
}
登録する要素が複数ある場合はループ処理で、1つ1つクリックイベントを登録する必要があります。
マウスオーバーイベント
const elem = document.querySelector('.button');
/* マウスカーソルが被さった時の処理 */
elem.addEventListener('mouseenter', function(){
// 何らかの処理
});
/* マウスカーソルが離れた時の処理 */
elem.addEventListener('mouseleave', function(){
// 何らかの処理
});
/* マウスカーソルが被さった時の処理 */
elem.addEventListener('mouseover', function(){
// 何らかの処理
});
/* マウスカーソルが離れた時の処理 */
elem.addEventListener('mouseout', function(){
// 何らかの処理
});
/*----こんな書き方もできる----*/
/* マウスカーソルが被さった時の処理 */
elem.onmouseenter = function() {
// 何らかの処理
}
/* マウスカーソルが離れた時の処理 */
elem.onmouseleave = function() {
// 何らかの処理
}
/*---複数の要素に対してマウスオーバーを設定する場合----*/
const target = document.querySelectorAll('.target');
for (let i = 0; i < target.length; i++) {
target[i].addEventListener('mouseenter', function(){
// マウスカーソルが被さった時の処理
});
}
mouseenterとmouseover、それからmouseleaveとmouseoutでは要素を判定する考え方が違います。違いについては以下の記事がわかりやすいです。
基本的にはmouseenterとmouseleaveを使うことが多いでしょう。
数秒後に実行する
/*------ 基本 ------*/
const timeAction = function(){
console.log("3秒経過"); //3秒後に「3秒経過」と表示させる
}
setTimeout(timeAction, 3000);
/*------ タイマーが複数ある場合 ------*/
setTimeout(function() {
console.log('1つ目のタイマー'); //2秒経ってから「1つ目のタイマー」と表示させる
setTimeout(function() {
console.log('2つ目のタイマー'); //3秒後(2秒 + 1秒)に「2つ目のタイマー」と表示させる
}, 1000);
}, 2000);
/*------ 引数がある場合 ------*/
function hello(name) {
console.log('Hello,' + name);
}
setTimeout(hello, 2000, 'Taro'); //2秒後に「Hallo,Taro」と表示させる
/*------ 時間をリセットする ------*/
const elem = document.querySelector('.button');
let timeoutId;
elem.addEventListener("click", function () {
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
console.log("Hello");
}, 1000);
});
特にクリックイベントにsetTimeoutを設定している場合、clearTimeoutでセットした時間をクリアしないと、クリックされた分だけsetTimeoutの処理が実行されてしまうので注意しましょう。
リサイズイベント
let timeoutId ;
window.addEventListener( "resize", function () {
// リサイズを停止して0.3秒後に実行する
clearTimeout( timeoutId ) ;
timeoutId = setTimeout( function () {
// 処理内容
}, 300 ) ;
} ) ;
setTimeoutと一緒にresizeイベントを設定しないと、少しでも画面サイズが変更されるだけでイベントが実行されます。
画面サイズ変更が行われて停止してから0.3秒後に処理を実行させるなど、何度もイベントが発生しないようにすれば、ブラウザへの負荷を下げることができます。
Mediaクエリを使って処理を分ける
//【768px以下】という条件を格納
const media_width = window.matchMedia("(max-width: 768px)");
//処理の内容
function checkBreakPoint() {
if (media_width.matches) { //768px以下なら
} else { //769px以上なら
}
}
// ブレイクポイントの瞬間に発火
//media_width.addListener(checkBreakPoint); ← 非推奨。でもIE11とsafariでも機能する
media_width.addEventListener("change", checkBreakPoint); //こっちが推奨だけど、safariとIE11で機能しない
// 初回チェックで発火
checkBreakPoint();
CSSのmediaクエリと同じように、JavaScriptでもmediaクエリを使って処理を分けることができます。17行目でサイトが読み込まれたときにまず1回発火させます。あとは14行目の処理によって、画面サイズが768pxをすぎる度に発火させています。
matchMediaはIE11に対応していません。
対応させたい場合は以下のpolyfillを読み込ませましょう。
<script type="text/javascript" src="https://polyfill.io/v3/polyfill.min.js?features=matchMedia"></script>
画面推移
/*---- 通常の遷移 ----*/
window.location.href = 'パス名';
/*---- 新タブを開かせて推移させる ----*/
window.open('パス名', '_blank');
/*---- 数秒後にクリックした要素のリンク先へ移動 ----*/
let elems = document.querySelectorAll(".target");
let timer;
for (let i = 0; i < elems.length; i++) {
elems[i].addEventListener("click", function (elem) {
clearTimeout(timer);
elem.preventDefault(); //ページ推移を1度キャンセル
const url = elem.getAttribute("href"); //クリックした要素のurlを取得
timer = setTimeout(function () {
window.location.href = url; //0.5秒後に取得したurlに移動
}, 500);
});
}
リンクをクリックしたときに、すぐのページに飛ばず、少しエフェクトをかけてから移動させるときに使えます。
おわりに
脱jQueryする理由については、手軽に操作できるjQueryに慣れてしまうと、本来のJavaScriptの挙動を理解できず、自分のステップアップにならないと感じたためです。
決してjQueryが使えないとか、時代遅れというわけではなく、理解したうえで使えているのであればまったく問題ありません。非常に便利なフレームワークです。
これから新しい言語を習得していきたい方や、複雑なコードを書いていきたい方はネイティブのJavaScriptを習得していきましょう。