ソフトウェア 公開日 2026.04.26 更新日 2026.06.13

Web Workerとは?重い処理でUIを止めないための仕組み

Web Workerとは何かを、メインスレッドとの違い、UIが固まる理由、できること・できないこと、Service Workerとの違い、どんな場面で使うのかまで整理します。

先に要点

  • Web Worker は、ブラウザのメインスレッドとは別で JavaScript を動かす仕組みです。重い計算をワーカーへ逃がすと、画面操作や描画が止まりにくくなります。
  • 逃がす目安は「メインスレッドを 50ms 以上ブロックする処理」。1 つの処理が 1 秒近く回るなら、ほぼ確実に Web Worker の検討対象です。
  • DOM は直接触れません。メインスレッドとは postMessage() でやり取りしますが、大きいデータをそのまま渡すとコピーコストでかえって遅くなるので、Transferable で渡し方を工夫します。
  • Service Worker とは別物で、Web Worker は主に「UI を固めないための処理分離」に使います。

JavaScript の処理が重くて画面が固まる、入力が引っかかる、スクロールがカクつく、というのはフロントエンドでよくある悩みです。
そのとき候補に上がるのが Web Worker です。

この記事では、Web Worker とは何かを、なぜ UI が止まるのか何をワーカーに逃がすのか何は逃がせないのか、そしてどこで失敗しやすいのかの順で、判断基準の数値と失敗例まで含めて整理します。
関連して ジョブキューとは?重い処理を後ろに回す理由Service Workerとは?PWAのキャッシュやオフライン対応を支える仕組みを解説 もつながります。

Web Workerとは何か

Web Worker は、Web アプリのメイン実行スレッドとは別のバックグラウンドスレッドで JavaScript を動かす仕組み です。
MDN でも、Web Workers API は「メイン実行スレッドとは別のバックグラウンドスレッドで動かす仕組み」と説明されています。

ブラウザの通常の JavaScript は、画面の描画やイベント処理に近いメインスレッドで動きます。
そこに重い計算が乗ると、クリック、入力、スクロール、アニメーションまで待たされます。

Web Worker は、その重い計算の一部を別スレッドへ分けるための仕組みです。

なぜUIが固まるのか

ここが分かると、Web Worker の役割も見えやすくなります。

ブラウザでは、メインスレッドが次のような仕事をまとめて担当しています。

イベント処理

クリック、入力、タップを受け取りハンドラを呼ぶ。

JavaScript 実行

あなたの書いた計算ロジックを 1 本のスレッドで順番に処理する。

レイアウト・描画

DOM の変化を計算し直し、画面に反映する。

アニメーション

1 秒間に 60 回(16.6ms ごと)フレームを描こうとする。

これらは同じ 1 本のスレッドが順番にこなしているので、JavaScript が長く回っている間は、他の仕事がすべて後ろで待たされます。

「重い」の数値的な目安

体感の話を数値に落とすと分かりやすくなります。Google の web.dev では、メインスレッドを 50ms 以上専有するタスクを「Long Task(長いタスク)」と定義しています。アニメーションは 16.6ms ごとに 1 フレーム描く必要があるため、50ms 回っただけで 3 フレーム分のカクつきが出る計算です。

応答性の指標 INP(Interaction to Next Paint)では、200ms 以下なら良好、500ms を超えると不良とされています。つまり、

  • 50ms を超える → そろそろ分割やワーカー化を考える
  • 200ms を超える → ユーザーが「ワンテンポ遅い」と感じ始める
  • 500ms 〜 1 秒を超える → 「固まった」とはっきり認識される。Web Worker のほぼ確実な対象

という線引きになります。本記事ではこの「1 秒近くブロックするなら逃がす」を実務上の目安として使います。

Web Workerで何がうれしいのか

いちばん大きい利点は、重い処理と UI 操作を分けられること です。

1. 入力やスクロールが引っかかりにくくなる

計算処理をワーカーに逃がすと、メインスレッドは入力受付や描画に集中できます。
たとえば 10 万行の配列を JavaScript でソート・集計すると、端末によっては 800ms〜数秒メインスレッドを専有します。この間はスピナーすら回りません(描画もメインスレッドだから)。これをワーカーへ移すと、計算中もスピナーが滑らかに回り、検索ボックスへの入力も普通に効きます。処理時間そのものは短くなりませんが、「待っている間に操作できる」体感差が生まれるのがポイントです。

2. 大きいデータ処理をブラウザ側で持ちやすい

たとえば次のような処理は相性がよいです。

  • 大量 JSON の整形
  • CSV の読み込みや変換
  • グラフ用データの集計
  • 画像や音声の前処理
  • 暗号化や圧縮のような CPU 寄り処理

3. メインコードの責務を分けやすい

UI は UI、計算は Worker という形にすると、コードの責務分離としても分かりやすくなります。

ワーカー化の前後で体感はどう変わるか

「重いソートをワーカーに移したら何が変わるのか」を、メインスレッドでやった場合と並べて整理します。処理そのものの CPU 時間は変わらないこと、変わるのは「その間に UI が生きているか」であることが要点です。

観点 メインスレッドで処理 Web Worker で処理
計算の所要時間 例: 約 800ms 例: 約 800ms(ほぼ変わらない)
処理中のクリック・入力 効かない(キューに溜まる) 普通に効く
処理中のスピナー/アニメ 止まる(描画もブロック) 滑らかに回る
INP への影響 800ms 級のブロックで不良域へ メイン側は短いまま、良好を保ちやすい
実装コスト 低い(その場で書ける) ファイル分離・メッセージ設計が必要

ここで誤解しやすいのは、Web Worker は「速くする」道具ではなく「止めない」道具だという点です。8 コア CPU で並列に走らせて速くなるケースもありますが、まず最初に得られる効果は「重い処理中も画面が生きている」ことです。

何でもWeb Workerに入れればいいわけではない

ここは大事です。
Web Worker は便利ですが、重そうなものを全部投げる箱ではありません。逃がすかどうかは、前述の「メインスレッドを 50ms〜1 秒以上ブロックするか」で判断します。100ms で終わる処理をわざわざワーカーへ出すと、後述する通信コストの方が大きくなり、かえって遅くなります。

Web Workerでできないこと

MDN が説明している通り、ワーカーでは DOM を直接操作できません
window の通常ページ向け API もそのまま全部使えるわけではありません。

つまりワーカーの中では、

  • ボタンを書き換える
  • 画面にエラーを表示する
  • 要素を追加する

といった UI 操作は直接できません。こうした反映は、ワーカーからメインスレッドへ結果を返し、メイン側で行います。一方で fetchWebSocketIndexedDB、タイマー、WebAssembly などはワーカー内でも使えます。

メインスレッドとどうやり取りするのか

Web Worker は、メインスレッドと postMessage() を使ってメッセージでやり取りします。
導入手順はおおむね次の通りです。

読み込み中...

メイン側

const worker = new Worker("worker.js");
worker.postMessage({ numbers: [1, 2, 3] });

worker.onmessage = (event) => {
  console.log(event.data);
};

worker側

self.onmessage = (event) => {
  const result = event.data.numbers.reduce((sum, n) => sum + n, 0);
  self.postMessage({ result });
};

この構成なので、UI と計算がきれいに分かれる代わりに、状態の受け渡し設計が必要 です。

postMessageは「コピー」だと意識する

ここが最大の落とし穴です。postMessage() で渡したデータは、既定では構造化複製(structured clone)で丸ごとコピーされてからワーカーへ届きます。送る側と受け取る側で同じデータが二重にメモリに乗り、コピーの計算自体もメインスレッドで走ります。

小さなオブジェクトなら無視できる(サブミリ秒)コストですが、データが大きいと無視できません。Chrome for Developers のベンチマークでは、32MB の ArrayBuffer を構造化複製で往復させると約 302msかかりました。せっかく計算をワーカーへ逃がしても、この 302ms はメインスレッドのコピー処理として戻ってきます。

これを避けるのが Transferable Objects(所有権の移譲)です。ArrayBuffer などはコピーせず所有権だけを渡せるため、同じ 32MB の往復が約 6.6msに縮みます(その代わり送信元では使えなくなります)。

// 大きいバッファはコピーせず「移譲」する
worker.postMessage({ buffer }, [buffer]); // 第2引数に Transferable を列挙

つまり判断は二段構えです。そもそも 50ms 以下で終わる処理は逃がさない。逃がすなら、大きいデータは Transferable か SharedArrayBuffer で渡す。これを守らないと、ワーカー化したのに前より遅い、という典型的な失敗に陥ります。

ありがちな失敗とその回避

現象から原因をたどれるように、つまずきやすいパターンを整理します。

現象 原因 確認手順 回避
ワーカー化したのに前より遅い 大きいオブジェクトを postMessage でコピー渡ししている DevTools の Performance で postMessage 前後の Long Task と「Structured Clone」の時間を確認 ArrayBuffer は Transferable で渡す。または SharedArrayBuffer を使う
1 回ごとは速いのに連打すると固まる 頻繁な小さいメッセージの往復回数が多すぎる Performance でメッセージ往復の回数とハンドラ実行を計測 呼び出しをまとめる(バッチ化)。逃がす粒度を大きくする
軽い処理を逃がしたのに体感が悪化 処理が 50ms 未満で、ワーカー起動・通信コストの方が重い メイン実行時の Long Task が出ているか確認(出ていないなら不要) 逃がさずメインで処理する。逃がす基準を 50ms〜1 秒で線引きする
ワーカー内で document が undefined と出る ワーカーから DOM を触ろうとしている エラーの行を確認(window / document 参照) 結果を postMessage で返し、DOM 更新はメイン側で行う

いちばん多いのは 1 行目、「コピーコストでかえって遅くなる」です。ワーカーに移したのに体感が改善しないときは、まず postMessage で何 MB 渡しているかを疑ってください。

どんな種類があるのか

MDN では、Web Workers API の中にいくつかのワーカー種類があります。

Dedicated Worker

もっとも基本的な Web Worker です。
単一のスクリプトから使う前提で、まず普通に「Web Worker」と言うとこれを指すことが多いです。

Shared Worker

複数のスクリプトや複数のウィンドウから共有できるワーカーです。
ただし扱いは少し複雑で、ポート経由の通信が必要です。

Service Worker

ここは混同しやすいですが、Service Worker は Web Worker と同じ役割ではありません。
MDN でも、Service Worker はアプリ、ブラウザ、ネットワークの間に入るプロキシのような存在として説明されています。

Web Worker

UI を止めないための計算・処理分離。CPU 寄りの重い仕事を逃がす。

Service Worker

キャッシュ、通信制御、オフライン、Push など。ネットワークのプロキシ役。

どんな場面で使うべきか

次のようなときは、Web Worker を検討する価値があります。いずれも「メインスレッドを長くブロックするか」が判断の軸です。

1. 重い処理で入力や描画が止まる

フォーム入力中や検索中に固まる、表の並び替えで数秒止まる、グラフ生成で UI が引っかかるなら候補です。DevTools の Performance タブで赤い Long Task(50ms 超)が出ているかどうかを最初に確認しましょう。

2. ブラウザ側で大きいデータを扱う

サーバーへ送る前にブラウザで前処理する場合も相性がよいです。
たとえば CSV アップロード前の検査や、画像の縮小、ローカル集計などです。

3. 計算ロジックと画面ロジックを分けたい

保守性の面でも、計算専門の処理を分離したいときに向いています。

逆に使わない方がいい場面

なんでもワーカー化すると、今度は設計と通信の方が重くなります。

1. 処理が軽い(50ms 未満)

軽い処理なら、ワーカー起動やメッセージ受け渡しの方が面倒で、コストも大きくなります。

2. すぐDOMを触りたい

結果を返してからメインで DOM 更新する必要があるので、UI の細かい反応を直接書きたい処理とは相性がよくありません。

3. 状態共有が複雑すぎる/大きいデータを頻繁に往復する

大量の状態を頻繁にやり取りするなら、分離メリットより通信(コピー)コストの方が勝つことがあります。

Web Workerに関するよくある質問

Q. どのくらい重い処理から Web Worker を考えるべき?

A. 目安はメインスレッドを 50ms 以上ブロックする処理です。50ms は web.dev の Long Task の定義でもあります。1 つの処理が 200ms を超えるとユーザーが遅さを感じ始め、500ms〜1 秒を超えると「固まった」と認識されます。1 秒近く回る処理ならほぼ確実に逃がす対象です。逆に 50ms 未満なら逃がさない方が速いことが多いです。

Q. ワーカー化すれば処理は速くなりますか?

A. 多くの場合、計算時間そのものはほぼ変わりません。変わるのは「処理中に UI が動くか」です。8 コア CPU で複数ワーカーに分割すれば速くなる余地もありますが、まず得られる効果は「重い処理中もクリックやスピナーが生きている」という応答性です。

Q. ワーカーに移したのに前より遅くなりました。なぜ?

A. postMessage() が既定でデータを丸ごとコピー(構造化複製)するためです。32MB の ArrayBuffer をコピー渡しすると往復で約 302ms かかります。Transferable で所有権を移譲すれば同じ往復が約 6.6msに縮みます。DevTools の Performance で Structured Clone の時間を確認してください。

Q. Web Worker で DOM 操作できますか?

A. できません。UI スレッド = メイン、計算スレッド = Worker の分離が前提です。ワーカーから postMessage でメインへ結果を返し、メイン側で DOM を更新します。ワーカー内で document を参照すると undefined になります。

Q. どんな処理を Worker に逃がすべき?

A. 大量データの並べ替えやフィルタ、画像処理、PDF 生成、暗号化・復号化、機械学習推論、オフライン同期などです。共通点は「CPU 寄りで、まとまった時間メインスレッドを占有する」ことです。

Q. SharedArrayBuffer は使えますか?

A. はい。Cross-Origin Isolation(COOPCOEP ヘッダー)を設定すれば使えます。メインとワーカーでメモリを共有でき、postMessage のコピーオーバーヘッドそのものを避けられます。大きいバッファを頻繁にやり取りする場面で有効です。

Q. ライブラリ(ReactVue)で Worker を使うには?

A. React なら useWorker 系フック、Vue なら vue-worker、汎用では Comlink が定番です。Comlink は postMessage を関数呼び出しのように抽象化してくれるので、メッセージ設計の手間が減ります。ただし大きいデータの渡し方(Transferable)は自分で意識する必要があります。

Q. WebAssembly と組み合わせると何が嬉しい?

A. C/C++、Rust、Go で書いた高性能コードをワーカー内で実行できます。画像・動画処理、ゲーム、機械学習推論などで、UI を止めずに高速処理できます。重い計算をワーカーへ、その中の数値処理を WebAssembly へ、という二段構えになります。

まとめ

Web Worker は、ブラウザのメインスレッドとは別で JavaScript を動かし、重い処理で UI を止めにくくする仕組み です。

大事なのは、

  • UI はメインスレッド、重い計算はワーカー、結果はメッセージで受け渡す
  • 逃がす基準は「50ms〜1 秒以上ブロックするか」。軽い処理は逃がさない
  • 大きいデータは Transferable か SharedArrayBuffer で渡し、コピーコストで遅くしない

という役割分担と判断基準です。

「画面が固まる」問題の答えが必ず Web Worker とは限りませんが、CPU 寄りで重い処理をブラウザ側に持ちたいときにはかなり有力です。
一方で、DOM を直接触れないこと、postMessage はコピーであること、Service Worker とは別物であることは最初に押さえておくと混乱しません。

このあと一緒に読みたい

  1. Service Workerとは?PWAのキャッシュやオフライン対応を支える仕組みを解説
  2. ジョブキューとは?重い処理を後ろに回す理由
  3. フロントエンド、バックエンド、クライアントサイド、サーバーサイドの意味

参考リンク

あとで見返すならここで保存

読み終わったあとに残しておきたい記事は、お気に入りからまとめて辿れます。