JavaScript で DOM を操作するとき、まず必要になるのが「どの要素を操作するか」を取り出す作業です。この記事では、CSS セレクタを使って要素を取得できる document.querySelector() と document.querySelectorAll() の使い方を、最小のコードから実践例まで順番に解説します。getElementById などの従来メソッドとの違いや、取得した要素を扱うときのつまずきポイントもあわせて紹介するので、初めて DOM 操作に触れる方でも迷わず使えるようになります。
目次
querySelector / querySelectorAll とは
document.querySelector() と document.querySelectorAll() は、CSS セレクタの文字列を渡して、それにマッチする HTML 要素を取得するメソッドです。普段 CSS で .box や #header、ul li のように書いているセレクタを、そのまま JavaScript で使えるのが大きな特徴です。
両者の違いはシンプルで、querySelector() はマッチした要素のうち「最初の1つ」だけを返し、querySelectorAll() はマッチした要素を「すべて」まとめて返します。取得した要素は、テキストを書き換えたり、スタイルを変えたり、クリックイベントを付けたりといった操作の起点になります。
基本の使い方
まずは一番シンプルな例です。次のような HTML があるとします。
<h1 id="title">ページタイトル</h1>
<p class="note">1つ目のメモ</p>
<p class="note">2つ目のメモ</p>
この HTML に対して、querySelector() で要素を1つ取得してみます。引数には CSS と同じセレクタを文字列で渡します。id を指定するなら #、class を指定するなら . を先頭に付けます。
// id="title" の要素を取得
const title = document.querySelector("#title");
console.log(title.textContent); // "ページタイトル"
// class="note" の「最初の」要素を取得
const firstNote = document.querySelector(".note");
console.log(firstNote.textContent); // "1つ目のメモ"
.note は2つありますが、querySelector() は最初に見つかった1つだけを返す点に注目してください。すべてのメモを取得したい場合は、次の querySelectorAll() を使います。
// class="note" の要素を「すべて」取得
const notes = document.querySelectorAll(".note");
console.log(notes.length); // 2
// 1つずつ取り出して処理する
notes.forEach((note) => {
console.log(note.textContent); // "1つ目のメモ" → "2つ目のメモ"
});
CSS セレクタが使えるので、document.querySelectorAll("ul.menu > li") のように要素・クラス・子孫関係を組み合わせた細かい指定もできます。属性セレクタ input[type="checkbox"] や疑似クラス li:first-child も同じように指定可能です。
getElementById などとの違い
要素を取得するメソッドには、getElementById() や getElementsByClassName() といった従来からのものもあります。querySelector 系との大きな違いは、CSS セレクタを自由に使えること、そして戻り値の形が異なることです。主な違いを次の表にまとめました。
| メソッド | 引数 | 戻り値 |
|---|---|---|
querySelector() | CSS セレクタ | 最初にマッチした1要素(なければ null) |
querySelectorAll() | CSS セレクタ | マッチした全要素の NodeList |
getElementById() | id 名(# なし) | 該当する1要素(なければ null) |
getElementsByClassName() | クラス名(. なし) | HTMLCollection(複数) |
注意したいのは引数の書き方です。getElementById("title") は # を付けないのに対し、querySelector("#title") は CSS と同じく # が必要です。混同して querySelector("title") と書くと、これは「<title> という要素」を探す意味になってしまうので気をつけてください。
もう一つの違いは「動的かどうか」です。getElementsByClassName() が返す HTMLCollection は、DOM の変化に追従するライブ(動的)なコレクションですが、querySelectorAll() が返す NodeList は取得した瞬間の状態を切り取った静的なものです。後から要素を追加しても、すでに取得済みの NodeList の中身は増えません。
querySelectorAll が返す NodeList の扱い方
querySelectorAll() の戻り値は配列のように見えますが、実際は NodeList という別物です。NodeList は length プロパティを持ち、forEach() で反復できますが、配列が持つ map() や filter()、reduce() などのメソッドは使えません。
const notes = document.querySelectorAll(".note");
// forEach は使える
notes.forEach((note) => console.log(note.textContent));
// map はそのままだと使えない(TypeError になる)
// const texts = notes.map((note) => note.textContent); // ✗
map() などの配列メソッドを使いたいときは、いったん本物の配列に変換します。Array.from() に渡すか、スプレッド構文 [...notes] で展開すれば、通常の配列として扱えるようになります。
const notes = document.querySelectorAll(".note");
// Array.from で配列に変換
const texts = Array.from(notes).map((note) => note.textContent);
console.log(texts); // ["1つ目のメモ", "2つ目のメモ"]
// スプレッド構文でも同じことができる
const texts2 = [...notes].map((note) => note.textContent);
取得した要素を操作してみる
実際の開発では、取得した要素にイベントを付けたりスタイルを変えたりして使います。次のデモは、ボタンに click イベントを設定し、押されたら querySelector() で取得した段落のテキストと色を書き換え、さらに querySelectorAll() で取得したリスト項目すべてに番号と色を付ける例です。タブを切り替えてコードを確認し、プレビューのボタンを押して動きを試してみてください。
<div class="demo">
<p class="message">ボタンを押すと、この文章のスタイルが変わります。</p>
<ul class="list">
<li>りんご</li>
<li>みかん</li>
<li>ぶどう</li>
</ul>
<button id="run">スタイルを変える</button>
</div>
body {
margin: 0;
font-family: sans-serif;
color: #333;
}
.demo {
padding: 24px;
}
.message {
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
}
.list {
padding-left: 20px;
}
button {
margin-top: 12px;
padding: 8px 16px;
border: none;
border-radius: 6px;
background: #4f46e5;
color: #fff;
font-weight: bold;
cursor: pointer;
}
// 最初の1つだけ取得(querySelector)
const button = document.querySelector("#run");
button.addEventListener("click", () => {
// クラス .message の最初の要素を取得して色を変える
const message = document.querySelector(".message");
message.style.color = "#e11d48";
message.style.fontWeight = "bold";
message.textContent = "querySelector でこの文章を取得して書き換えました。";
// .list の中の li をすべて取得(querySelectorAll → NodeList)
const items = document.querySelectorAll(".list li");
// NodeList は forEach で1つずつ処理できる
items.forEach((item, index) => {
item.style.color = "#4f46e5";
item.textContent = (index + 1) + ". " + item.textContent;
});
});
このように、querySelector() で1つの要素をピンポイントに操作し、querySelectorAll() と forEach() で複数の要素をまとめて処理する、という流れが基本のパターンになります。
要素が取得できないときに確認すること
「指定したはずなのに要素が取れない」「null でエラーになる」というのは、DOM 操作で最初につまずきやすいポイントです。原因はいくつかのパターンに分かれるので、順番に確認していきましょう。
DOM の読み込み前にスクリプトが実行されている
もっとも多い原因がこれです。<head> の中で読み込んだ JavaScript は、HTML 本文より先に実行されることがあります。その時点では対象の要素がまだ存在しないため、querySelector() は null を返してしまいます。
対策としては、script タグに defer 属性を付けて HTML の解析後に実行させるか、DOMContentLoaded イベントの中で処理を行います。次のように書けば、要素が揃ったタイミングで安全に取得できます。
// DOM の構築が終わってから実行する
document.addEventListener("DOMContentLoaded", () => {
const title = document.querySelector("#title");
console.log(title.textContent);
});
セレクタの書き方が間違っている
セレクタの記号の付け忘れや綴りの間違いもよくあります。id なら #、class なら . が必要で、これを忘れると別の意味になってしまいます。また、HTML 側のクラス名と JavaScript 側で指定した文字列が1文字でも違えば、当然マッチしません。うまく取れないときは、ブラウザの開発者ツールのコンソールで同じセレクタを試し、想定した要素が返ってくるか確認すると原因を切り分けやすくなります。
該当する要素が存在せず null になっている
querySelector() はマッチする要素がないと null を返します。その null に対して .textContent や .style を使おうとすると、「Cannot read properties of null」というエラーで処理が止まります。要素があるか分からない場合は、操作する前に存在チェックを入れておくと安全です。
const banner = document.querySelector(".banner");
// 要素が見つかったときだけ処理する
if (banner) {
banner.style.display = "none";
}
なお、querySelectorAll() の場合はマッチする要素がなくても null ではなく、長さ0の空の NodeList を返します。そのため forEach() で回しても安全に何も実行されないだけで、エラーにはなりません。
まとめ
querySelector() と querySelectorAll() は、CSS セレクタをそのまま使って要素を取得できる便利なメソッドです。querySelector() は最初にマッチした1要素を返し、見つからなければ null になります。querySelectorAll() はマッチした全要素を NodeList として返し、forEach() で反復できますが、map() などを使いたいときは Array.from() やスプレッド構文で配列に変換します。
うまく取得できないときは、スクリプトの実行タイミング(DOM 読み込み前ではないか)、セレクタの記号や綴り、そして null チェックの有無を順に確認すれば、たいていの原因にたどり着けます。まずはこの2つのメソッドを押さえておけば、DOM 操作の入り口は十分にカバーできます。