私はこれまでDOM操作にjQueryを使ってきましたが、そろそろ卒業しようと思い、バニラJSで同じことをするにはどう書くのかを自分用にまとめ直しました。ついでに、jQueryでは意識していなかったけど実務でよく使うパターンも追加しています。

要素の取得

まずはDOM操作の起点となる、要素の取得方法です。

querySelector / querySelectorAll

CSSセレクタで要素を取得します。querySelectorは最初の1つ、querySelectorAllは該当する全要素を返します。

// 最初の1つを取得
const el = document.querySelector('.target');

// 該当する全要素を取得(NodeList)
const els = document.querySelectorAll('.target');

// NodeListはforEachで回せる
els.forEach((el) => {
  console.log(el.textContent);
});

querySelectorAllが返すNodeListは配列ではありませんが、forEachはそのまま使えます。mapfilterを使いたい場合は[...els]でスプレッド構文を使って配列に変換しましょう。

closest

自分自身を含む祖先要素の中から、セレクタに一致する最も近い要素を取得します。イベントハンドラの中で親要素を辿りたいときに重宝します。

document.querySelector('.child').addEventListener('click', (e) => {
  const card = e.target.closest('.card');
  console.log(card); // .card要素を取得
});

テキスト・HTMLの操作

textContent

要素のテキストを取得・設定します。jQueryの.text()に相当します。

const el = document.querySelector('.target');

// 取得
console.log(el.textContent);

// 設定
el.textContent = '新しいテキスト';

innerHTML

要素内のHTMLを取得・設定します。jQueryの.html()に相当します。

const el = document.querySelector('.target');

// 取得
console.log(el.innerHTML);

// 設定
el.innerHTML = '<strong>太字テキスト</strong>';

ユーザー入力をそのままinnerHTMLに入れるとXSS(クロスサイトスクリプティング)のリスクがあります。ユーザー入力を表示する場合はtextContentを使いましょう。

要素の追加・挿入

append / prepend

子要素の末尾または先頭に要素を追加します。jQueryの.append() / .prepend()とほぼ同じ感覚で使えます。

const parent = document.querySelector('#parent');

// 末尾に追加
const newEl = document.createElement('p');
newEl.textContent = '新しい段落';
parent.append(newEl);

// 先頭に追加
parent.prepend(newEl);

// 文字列も直接追加できる
parent.append('テキストノード');

実行前後のHTMLの変化は以下の通りです。

<!-- 実行前 -->
<div id="parent">
  <p>既存の段落</p>
</div>

<!-- append実行後 -->
<div id="parent">
  <p>既存の段落</p>
  <p>新しい段落</p>  <!-- 末尾に追加された -->
</div>

<!-- prepend実行後 -->
<div id="parent">
  <p>新しい段落</p>  <!-- 先頭に追加された -->
  <p>既存の段落</p>
</div>

before / after

指定した要素の前後に兄弟要素として挿入します。

const target = document.querySelector('#element');

const newDiv = document.createElement('div');
newDiv.textContent = '後ろに追加される';

// 後ろに挿入
target.after(newDiv);

// 前に挿入
target.before(newDiv);

実行前後のHTMLの変化は以下の通りです。append / prependが親子関係なのに対し、before / afterは兄弟関係になる点がポイントです。

<!-- 実行前 -->
<div id="element">ターゲット</div>

<!-- after実行後 -->
<div id="element">ターゲット</div>
<div>後ろに追加される</div>  <!-- 兄弟として後ろに挿入された -->

<!-- before実行後 -->
<div>後ろに追加される</div>  <!-- 兄弟として前に挿入された -->
<div id="element">ターゲット</div>

insertAdjacentHTML

HTML文字列を指定した位置に挿入できる便利なメソッドです。createElementを使わずにHTMLを直接追加したい場合に重宝します。

const el = document.querySelector('.target');

// 要素の直前(兄弟として)
el.insertAdjacentHTML('beforebegin', '<p>前に追加</p>');

// 要素の先頭(子として)
el.insertAdjacentHTML('afterbegin', '<p>先頭に追加</p>');

// 要素の末尾(子として)
el.insertAdjacentHTML('beforeend', '<p>末尾に追加</p>');

// 要素の直後(兄弟として)
el.insertAdjacentHTML('afterend', '<p>後に追加</p>');

4つのポジションの関係を図にすると以下のようになります。

<!-- beforebegin -->
<div class="target">
  <!-- afterbegin -->
  既存のコンテンツ
  <!-- beforeend -->
</div>
<!-- afterend -->

要素のラップ・削除・置換

要素をラップする

バニラJSにはjQueryの.wrap()に直接対応するメソッドはありませんが、数行で実装できます。

const target = document.querySelector('.item');
const wrapper = document.createElement('div');
wrapper.classList.add('wrapper');

// targetの前にwrapperを挿入し、その中にtargetを移動
target.before(wrapper);
wrapper.append(target);
<!-- 実行前 -->
<div class="item">コンテンツ</div>

<!-- 実行後 -->
<div class="wrapper">
  <div class="item">コンテンツ</div>  <!-- wrapperの中に移動した -->
</div>

remove

要素をDOMから削除します。

document.querySelector('.target').remove();

replaceWith

要素を別の要素に置き換えます。

const oldEl = document.querySelector('.old');
const newEl = document.createElement('div');
newEl.textContent = '置き換え後の要素';

oldEl.replaceWith(newEl);
<!-- 実行前 -->
<div class="old">古い要素</div>

<!-- 実行後 -->
<div>置き換え後の要素</div>  <!-- .oldが丸ごと置き換わった -->

属性・クラス・スタイルの操作

実務では要素の追加・削除だけでなく、クラスの付け外しや属性の変更も頻繁に使います。

classList

クラスの追加・削除・トグルを行います。jQueryの.addClass() / .removeClass() / .toggleClass()に相当します。

const el = document.querySelector('.target');

el.classList.add('active');
el.classList.remove('active');
el.classList.toggle('active');

// クラスの有無を確認
if (el.classList.contains('active')) {
  console.log('activeクラスがあります');
}

setAttribute / getAttribute / dataset

属性の取得・設定を行います。data-*属性はdatasetプロパティで簡単にアクセスできます。

const el = document.querySelector('.target');

// 汎用的な属性操作
el.setAttribute('aria-hidden', 'true');
console.log(el.getAttribute('href'));
el.removeAttribute('disabled');

// data属性はdatasetが便利
// <div data-user-id="123" data-role="admin">
console.log(el.dataset.userId);  // "123"
console.log(el.dataset.role);    // "admin"
el.dataset.status = 'active';    // data-status="active"が追加される

style

インラインスタイルを直接操作します。jQueryの.css()に相当します。

const el = document.querySelector('.target');

el.style.display = 'none';
el.style.backgroundColor = '#F59E0B';

// CSS変数の操作もできる
el.style.setProperty('--accent-color', '#F59E0B');

ただし、スタイルの変更は基本的にクラスの付け外しで対応し、styleプロパティの直接操作は動的に値が変わる場合に限定するのがおすすめです。

イベント委任(Event Delegation)

動的に追加された要素にもイベントを効かせたい場面は実務で頻繁にあります。jQueryの$(document).on('click', '.selector', fn)に相当するパターンです。

// 親要素にイベントを設定し、子要素をフィルタリング
document.querySelector('#list').addEventListener('click', (e) => {
  const item = e.target.closest('.item');
  if (!item) return;

  console.log('クリックされた:', item.textContent);
});

closestを使うことで、クリックされた要素が.itemの子要素(アイコンやテキスト)であっても、正しく.itemを取得できます。

実用サンプル:空のテキストの要素を非表示にする

指定したクラスの要素のテキストが空である場合、その要素を非表示にするサンプルです。

document.querySelectorAll('.target').forEach((el) => {
  if (el.textContent.trim() === '') {
    el.style.display = 'none';
  }
});

実用サンプル:空のセルがあるテーブル行を非表示にする

テーブルの行(<tr>)内に空のセルがある場合、その行を非表示にするサンプルです。

document.querySelectorAll('table tr').forEach((row) => {
  const td = row.querySelector('td');
  if (td && !td.innerHTML.trim()) {
    row.style.display = 'none';
  }
});

実用サンプル:アコーディオンの開閉

クリックでコンテンツを開閉するアコーディオンは、実務でもよく実装するパターンです。classList.toggleとイベント委任を組み合わせて実装します。

function initMenuToggle() {
  document.querySelector('.g-menu').addEventListener('click', (e) => {
    const trigger = e.target.closest('.g-menu__lv1');
    if (!trigger) return;

    trigger.classList.toggle('is-active');
  });
}

document.addEventListener("DOMContentLoaded", function () {
  initMenuToggle();
});
.g-menu__lv1 + div {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
}

.g-menu__lv1 + div > * {
  overflow: hidden;
}

.g-menu__lv1.is-active + div {
  grid-template-rows: 1fr;
}

開閉のアニメーションはCSSのgrid-template-rowsを使うと、高さが可変のコンテンツでもスムーズに対応できます。

まとめ

jQueryから移行するにあたって、特に押さえておきたいポイントをまとめます。

  • querySelector / querySelectorAllで要素を取得する
  • append / prepend / before / afterで要素を追加・挿入する
  • insertAdjacentHTMLでHTML文字列を直接挿入する
  • classListでクラスを操作する
  • closestとイベント委任で動的要素にも対応する

こうしてまとめてみると、バニラJSでもjQueryと同じ感覚で書けるメソッドが多いことに気づきます。個人的にはinsertAdjacentHTMLclosestを使ったイベント委任が便利で、この2つを知ってからjQueryに戻る理由がほぼなくなりました。