TECH · jQuery

jQueryを使わない書き方の対照表

サンプルとコード

01

表示切替とクラス操作

単独で開く ↗

jQuery の toggle と toggleClass に相当します。見た目の定義はすべてCSS側に置き、JavaScriptは「いまどの状態か」を宣言するだけにしています。

HTML
<div class="row">
  <button type="button" data-action="toggle">表示を切り替える</button>
  <button type="button" data-action="mute">薄くする</button>
  <button type="button" data-action="reset">元に戻す</button>
</div>

<div class="panel" id="panel">
  クラスを1つ足し引きするだけで、見た目の状態は CSS 側に全部書けます。JavaScript は「いまどの状態か」を宣言するだけで済み、色や余白の値を持たなくてよくなります。
</div>
CSS
.panel {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.1rem;
  color: var(--muted);
  line-height: 1.85;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
/* 「隠す」を CSS 側の状態として持たせる。JS は class を足し引きするだけ */
.panel.is-hidden {
  display: none;
}
.panel.is-muted {
  opacity: 0.45;
  transform: scale(0.99);
}
@media (prefers-reduced-motion: reduce) { .panel { transition: none; } }
JavaScript
const panel = document.getElementById("panel");

document.querySelectorAll("[data-action]").forEach((btn) => {
  btn.addEventListener("click", () => {
    const action = btn.dataset.action;

    // $('#panel').toggle() 相当
    if (action === "toggle") panel.classList.toggle("is-hidden");

    // $('#panel').toggleClass('is-muted') 相当
    if (action === "mute") panel.classList.toggle("is-muted");

    // $('#panel').removeClass('is-hidden is-muted') 相当
    if (action === "reset") panel.classList.remove("is-hidden", "is-muted");
  });
});
AIへの指示文
クラスの付け外しで表示を切り替える部品を、jQuery を使わずに作ってください。
要件:
- ボタンには data-action 属性で役割を持たせ、querySelectorAll でまとめて登録する。
- 表示の切替は classList.toggle、複数解除は classList.remove の可変長引数を使う。
- 見た目(display, opacity, transform)は必ず CSS 側のクラスに置く。
  JavaScript から style プロパティを直接書かない。
- prefers-reduced-motion: reduce のとき transition を無効にする。
- 日本語の本文なので line-height は 1.85、letter-spacing は 0 にする。
02

イベント委譲(動的に増える要素)

単独で開く ↗

jQuery の on による委譲に相当します。親に1つだけ登録し、closest で実際の対象を判定するので、後から追加した行にも自動で効きます。

HTML
<ul class="list" id="list">
  <li class="item"><span>企画書のたたき台をつくる</span><button class="del" type="button">削除</button></li>
  <li class="item"><span>参考サイトを 5 つ集める</span><button class="del" type="button">削除</button></li>
  <li class="item"><span>配色の候補を 3 案出す</span><button class="del" type="button">削除</button></li>
</ul>
<button class="add" type="button" id="add">+ 行を追加する</button>
CSS
.list { list-style: none; margin: 0; padding: 0; }
.item {
  display: flex; align-items: center; gap: 0.75rem;
  border: 1px solid var(--line); border-radius: 11px;
  padding: 0.7rem 0.9rem;
}
.item + .item { margin-top: 0.55rem; }
.item span { flex: 1; line-height: 1.7; }
.item .del {
  border: 0; background: none; color: var(--danger);
  font-size: 0.86rem; padding: 0.2rem 0.4rem; border-radius: 6px;
}
.item .del:hover { background: #fef2f2; }
.add {
  margin-top: 0.9rem; width: 100%;
  border: 1px dashed var(--line); background: none; color: var(--muted);
  border-radius: 11px; padding: 0.6rem;
}
.add:hover { color: var(--accent); border-color: var(--accent); }
JavaScript
const list = document.getElementById("list");

// $(document).on('click', '.del', ...) 相当。
// 親に1つだけ登録し、実際に押された要素を closest で判定する。
// 後から増えた行にも自動で効くのが委譲の利点。
list.addEventListener("click", (e) => {
  const btn = e.target.closest(".del");
  if (!btn || !list.contains(btn)) return;
  btn.closest(".item").remove();
});

let n = 4;
document.getElementById("add").addEventListener("click", () => {
  const li = document.createElement("li");
  li.className = "item";
  li.innerHTML =
    '<span>あとで決めるタスク ' + n++ + '</span><button class="del" type="button">削除</button>';
  list.append(li);
});
AIへの指示文
あとから増える行にも効く削除ボタンを、jQuery を使わずに作ってください。
要件:
- イベントは各ボタンではなく、親のリストに1つだけ addEventListener する。
- ハンドラの中で e.target.closest(".del") を使い、対象外なら early return する。
  親が本当にその要素を含むか contains でも確認する。
- 削除は closest(".item").remove() で行う。
- 追加ボタンは createElement と append で行を足す。
  追加後にイベントを登録し直さないことを、コメントで明示する。
- 削除ボタンの色は CSS 側で持ち、JavaScript は DOM 操作だけを担当する。
03

開閉アニメーション(slideToggle 相当)

単独で開く ↗

jQuery の slideToggle は高さを測ってpxで動かしますが、grid-template-rows を 0fr から 1fr へ遷移させれば高さを測らずに済みます。JavaScriptは状態の宣言だけです。

HTML
<div class="drawer">
  <button type="button" aria-expanded="false" aria-controls="d1">配送について</button>
  <div class="drawer-body" id="d1" data-open="false"><div>
    ご注文から 2 営業日以内に発送します。追跡番号は発送完了メールに記載します。
  </div></div>
</div>

<div class="drawer">
  <button type="button" aria-expanded="false" aria-controls="d2">支払い方法</button>
  <div class="drawer-body" id="d2" data-open="false"><div>
    クレジットカード、銀行振込、コンビニ払いに対応しています。法人のお客様は請求書払いもお選びいただけます。
  </div></div>
</div>
CSS
.drawer {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}
.drawer + .drawer { margin-top: 0.75rem; }
.drawer > button {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.15rem;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  line-height: 1.6;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.drawer > button::after {
  content: "";
  margin-left: auto;
  width: 9px; height: 9px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform 0.25s ease;
}
.drawer > button[aria-expanded="true"]::after { transform: rotate(-135deg); }
.drawer > button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* 高さのアニメーションは CSS に持たせる。JS は状態を切り替えるだけにする */
.drawer-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s ease;
}
.drawer-body[data-open="true"] { grid-template-rows: 1fr; }
.drawer-body > div { overflow: hidden; color: var(--muted); line-height: 1.85; }
.drawer-body[data-open="true"] > div { padding: 0 1.15rem 1.15rem; }

@media (prefers-reduced-motion: reduce) {
  .drawer-body, .drawer > button::after { transition: none; }
}
JavaScript
// jQuery なら $(this).next().slideToggle() の一行。
// 素の JS では「状態の宣言」だけを担当し、動きは CSS 側に置く。
document.querySelectorAll(".drawer > button").forEach((btn) => {
  btn.addEventListener("click", () => {
    const body = document.getElementById(btn.getAttribute("aria-controls"));
    const open = btn.getAttribute("aria-expanded") === "true";
    btn.setAttribute("aria-expanded", String(!open));
    body.dataset.open = String(!open);
  });
});
AIへの指示文
jQuery の slideToggle 相当の開閉を、JavaScript を最小限にして作ってください。
要件:
- 高さのアニメーションは JavaScript で測らない。
  中身を display: grid にして grid-template-rows を 0fr から 1fr へ遷移させる。
- JavaScript がやるのは、ボタンの aria-expanded と、
  中身の data-open 属性を true と false で切り替えることだけ。
- ボタンと中身は aria-controls と id で結ぶ。
- 山形記号は ::after で描き、aria-expanded="true" のとき回転させる。
- prefers-reduced-motion: reduce のとき transition を無効にする。
04

フォームの値をまとめて取る

単独で開く ↗

jQuery の serialize と serializeArray に相当します。FormData を URLSearchParams や Object.fromEntries に渡すだけで、同じ結果が標準機能だけで得られます。

HTML
<form class="form" id="form">
  <div class="field">
    <label for="q">キーワード</label>
    <input id="q" name="q" type="text" value="ステンレス 水筒" />
  </div>
  <div class="field">
    <label for="sort">並び替え</label>
    <select id="sort" name="sort">
      <option value="relevance">関連度が高い順</option>
      <option value="price_asc" selected>価格が安い順</option>
      <option value="new">新着順</option>
    </select>
  </div>
  <div class="field">
    <label for="max">上限価格</label>
    <input id="max" name="max" type="number" value="5000" min="0" step="100" />
  </div>
  <button type="submit">送信内容を確認する</button>
  <pre class="out" id="out" aria-live="polite">ここに送信内容が出ます</pre>
</form>
CSS
.form {
  background: #fff; border: 1px solid var(--line);
  border-radius: 14px; padding: 1.4rem;
  display: grid; gap: 1rem;
}
.field { display: grid; gap: 0.3rem; }
.field label { font-size: 0.88rem; font-weight: 600; line-height: 1.7; }
.field input, .field select {
  font: inherit; line-height: 1.7; color: inherit;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--line); border-radius: 9px;
}
.field input:focus, .field select:focus {
  outline: 0; border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.15);
}
.form button {
  font: inherit; line-height: 1.7; cursor: pointer;
  padding: 0.65rem 1.2rem; border: 0; border-radius: 9px;
  background: var(--accent); color: #fff; font-weight: 700;
}
.out {
  margin: 0; padding: 0.9rem 1rem;
  background: #0f1420; color: #e8ecf4;
  border-radius: 11px;
  font-family: var(--mono); font-size: 0.8rem; line-height: 1.75;
  white-space: pre-wrap; word-break: break-all;
}
JavaScript
const form = document.getElementById("form");
const out = document.getElementById("out");

form.addEventListener("submit", (e) => {
  e.preventDefault();

  // jQuery の serialize() 相当(クエリ文字列)
  const params = new URLSearchParams(new FormData(form));

  // serializeArray() をオブジェクトにしたもの相当
  const data = Object.fromEntries(new FormData(form));

  out.textContent =
    "クエリ文字列:\n" + params.toString() +
    "\n\nオブジェクト:\n" + JSON.stringify(data, null, 2);
});
AIへの指示文
フォームの入力値をまとめて取り出す処理を、jQuery を使わずに書いてください。
要件:
- submit イベントで e.preventDefault() してから処理する。
- クエリ文字列が欲しい場合は new URLSearchParams(new FormData(form)).toString()。
- オブジェクトが欲しい場合は Object.fromEntries(new FormData(form))。
  同名フィールドが複数ある場合は最後の値だけが残る点をコメントで注意する。
- 結果は pre 要素に textContent で入れる。innerHTML は使わない。
- 結果表示には aria-live="polite" を付ける。
- 各入力には label を for と id で結び、name を必ず付ける。

この技術について

jQueryが解いていた問題は、ブラウザごとにAPIがばらばらだったことでした。要素の取得方法が違う、イベントの登録方法が違う、非同期通信の書き方が違う。その差を吸収する層として、jQueryは圧倒的に正しい発明でした。そして、その問題はもう存在しません。

短く書くために必要だったものが、標準に入った

$('.item')document.querySelectorAll('.item') になりました。長いといえば長いですが、一度定数に入れれば済む程度の差です。addClassclassList.add$.ajaxfetch$(el).attr()getAttribute。かつてjQueryが短縮していた処理は、いまはほぼ同じ長さで標準機能として書けます。

この記事では、実務で書く頻度が高い4つ——クラス操作、イベント委譲、開閉アニメーション、フォームの値取得——を、動くサンプルとして並べました。どれも外部ライブラリを1つも読み込んでいません。

jQuery が短く書くために提供していた機能は、標準のAPIに入った $(sel) document.querySelectorAll(sel) $.ajax() fetch() $(el).addClass() el.classList.add() $(el).on(ev, sel, fn) el.addEventListener + closest() 左は「短く書くため」に必要だったもの。右は標準に入り、どのブラウザでも同じように動く
当時はブラウザごとに書き方が違ったことが本当の問題で、jQuery はそこを吸収していました。その差が消えたので、短さの差だけが残っています。

「置き換え」ではなく「置き場所を変える」もの

素直に1対1で置き換えられないものが一つあります。fadeInslideToggle のようなアニメーションです。

これらはJavaScriptが要素の高さや不透明度を毎フレーム書き換えることで動いています。同じことを素のJavaScriptでやろうとすると、たしかに面倒です。ただ、そもそも同じやり方をする必要がありません。動きの定義をCSSのtransitionに移し、JavaScriptは「開いた」「閉じた」という状態を宣言するだけにする。この記事のドロワーのサンプルは、まさにそれをやっています。JavaScriptは10行程度で、高さの計算には一切関与していません。

結果として、描画の最適化はブラウザに任せられ、動きの調整はCSSだけで完結します。置き換えというより、責務の置き場所を変える作業だと考えるほうが正確です。

全部書き直すのではなく、新規と触った箇所だけを標準に寄せていく 全部書き直す 動いている機能を まとめて作り替える 壊れたときに、原因の切り分けができない 置き場所を変える 既存のまま 新規は標準で 既存のまま 触るときだけ剥がす 1か所ずつ、戻せる単位で進む
動いているものを壊さないことが最優先です。新しく書く場所を標準に寄せるだけでも、jQuery に触れる面積は少しずつ減っていきます。

剥がすかどうかの判断基準

とはいえ、動いている既存サイトからjQueryを外すかどうかは別の判断です。目安は3つあります。

プラグインに依存していないか。 スライダーや日付選択などのjQueryプラグインを使っているなら、その置き換え先を先に決めないと外せません。ここが最大の障壁になることが多いです。

読み込みが実際にボトルネックになっているか。 jQueryは圧縮後で約30KBです。画像が数MBあるサイトで30KBを削っても、体感は変わりません。

触る人が読めるか。 チームがjQueryのコードを読める状態で安定して回っているなら、動いているものを急いで壊す理由はありません。新しく書く部分から素のJavaScriptにしていくのが、いちばんリスクの小さい進め方です。

jQuery を剥がすかどうかは、今後そのコードに手を入れるかどうかで決める 剥がしてよい ・使っているのが数行だけ ・他のプラグインに依存していない ・テストで確かめられる 残したほうがよい ・プラグインが前提にしている ・触れる人が限られている ・他に優先すべき改善がある jQuery が入っていること自体は問題ではない。判断の材料は「これから触るかどうか」 古いから剥がす、という理由だけで動くコードを触ると、得るものより失うものが大きい
新しさは判断の材料になりません。今後そのコードに手を入れる予定があるかで決めてください。触らない画面の依存は、そのままでも実害が出ません。

AIに書かせるときの一言

AIコーディング支援にUIの実装を頼むと、学習データの偏りから、いまだにjQuery前提のコードを返してくることがあります。指示に 「jQueryなどのライブラリは使わず、素のJavaScriptとCSSだけで書いてください」 と一行入れておくだけで、返ってくるコードの前提が揃います。各サンプルに添えたプロンプトにも、同じ趣旨を含めてあります。

AIに書かせるときは、jQuery を使うか使わないかを明示する 指示しない $(document).ready(…) jQuery 前提のコードが返る 一言添える 「jQuery を使わずに」 標準のAPIだけで返る 学習データに jQuery の例が大量にあるので、指定しないとそちらへ寄る 逆に、既存が jQuery なら「jQuery で」と書けば、混ざったコードを避けられる
どちらが正しいという話ではなく、黙っていると多数派に寄るというだけです。プロジェクトの前提を一言足せば、そのまま貼れるコードが返ってきます。

使いどころとつまずきどころ

向いている場面

  • 既存サイトからjQueryを外せるか判断したいとき
  • jQueryのコードを読んで意味を把握したいとき
  • 新規実装で依存を増やしたくないとき
  • AIに「jQueryを使わないで」と指示したいとき

つまずきやすい点

  • jQueryプラグインに依存している箇所は置換先を先に決める
  • $.ajax と fetch はエラー時の挙動が違う
  • slideUp / fadeIn 相当はCSS側に持たせるほうが軽い
  • $(document).ready は DOMContentLoaded と完全に同じではない

よくある質問

いま新規で作るサイトにjQueryは必要ですか。

ほとんどの場合、必要ありません。jQueryが解いていたのはブラウザ間のAPIの差でしたが、その差はもうありません。ただし既存サイトから剥がす話は別です。依存しているプラグインがあるなら、置き換え先を決めてから外してください。

$(document).ready と DOMContentLoaded は同じですか。

完全に同じではありません。DOMContentLoaded は発火後に登録したハンドラが呼ばれませんが、jQuery の ready は発火済みでも即座に実行されます。スクリプトを defer で読み込んでいるなら、そもそもどちらも不要で、直接書けば動きます。

$.ajax を fetch に置き換えるときの注意点は。

エラーの扱いが違います。$.ajax はHTTPステータスが4xxや5xxならエラー扱いですが、fetch は通信さえ成功すれば resolve します。res.ok を自分で確認して throw する処理を必ず書いてください。タイムアウトも fetch には無いので AbortSignal.timeout を使います。

fadeIn や slideUp の置き換えはどう書くのが良いですか。

JavaScriptで高さや不透明度を動かさず、CSSのtransitionに任せてください。JavaScript側はクラスやdata属性を切り替えるだけにします。描画の最適化がブラウザ任せになるぶん滑らかで、動きの調整もCSSだけで完結します。

同じ技術を使った完成例を、画面の型ごとにまとめています。作りたい画面が決まっているなら、こちらから探すほうが早いはずです。

ほかの技術