Three.jsは、ブラウザの3D描画APIであるWebGLを扱いやすくまとめたJavaScriptライブラリです。最新版はr185で、2026年6月25日に0.185.0、7月1日に修正版の0.185.1が公開されています。ライセンスはMITです。日本語の入門記事は2021年前後に書かれたものが多く残っていますが、その世代のコードはr185では動きません。配布形態そのものが入れ替わったためです。
この記事では、r185で実際に動く導入手順とglTFモデルの表示までを実装コードで示し、あわせて古い記事のコードが動かない原因と書き換え先、そして検索されることの多いバンドルサイズを実測値で示します。
まとめ
r185時点でのThree.jsの要点は次のとおりです。
- 導入は
import mapでCDNを読むか、npmとViteを使うかの2択。script要素でthree.min.jsを読む方法はr161で配布そのものが無くなった OrbitControlsやGLTFLoaderはthree/addons/から読み込む。r148でexamples/js/が削除され、THREE.GLTFLoaderのような名前空間経由の参照は成立しないTHREE.Geometryはr125で削除済み。頂点を直接組む場合はBufferGeometryを使うTHREE.Clockはr183で非推奨になり、後継のTimerがコアに入った- 最小構成でもgzip後130KBを下回らない。ツリーシェイキングによる削減幅は全量参照比で約30%にとどまる
以降で、それぞれの根拠と実装コードを順に説明します。
Three.jsの守備範囲とWebGLとの役割分担
WebGLを直接書く場合との実装量の差
WebGLはブラウザに標準搭載された低レベルの描画APIで、三角形を1枚出すだけでも頂点シェーダとフラグメントシェーダをGLSLで書き、バッファを確保し、行列を自分で計算する必要があります。Three.jsはこの層を包み、シーングラフ(Scene)、カメラ(PerspectiveCamera)、描画器(WebGLRenderer)という3つのオブジェクトの組み立てに置き換えます。立方体を1つ置いてライトを当てるまでが20行程度で済むのは、シェーダと行列演算をライブラリ側が持っているからです。
裏返すと、WebGLの概念そのものが消えるわけではありません。座標系、カメラの視錐台、マテリアルとライトの関係は理解が必要です。WebGL側の仕組みと対応ブラウザについてはWebGLとは?仕組み・できること・対応ブラウザとUnityビルドまで解説を参照してください。
なお、扱う対象が2Dのスプライトやグラフであれば、Three.jsは過剰です。2D描画に特化したWebGL/WebGPUで動く2D描画エンジンPixiJSの使い方のほうが、後述するバンドルサイズの面でも実装の素直さでも有利になります。
開発体制とライセンス・リリース間隔の実測
Three.jsは2010年にRicardo Cabello氏(Mr.doob)が公開し、現在もGitHubのmrdoob/three.jsで開発が続いています。配布物に同梱されるLICENSEはMITライセンスで、著作権表記は「2010-2026 three.js authors」です。商用利用時に必要なのは著作権表示とライセンス文の保持だけで、利用料や公開義務はありません。
リリース間隔は、npmレジストリの公開日時を並べると実態が見えます。2025年はr178が6月30日、r179が8月1日、r180が9月3日、r181が10月31日、r182が12月10日。2026年に入るとr183が2月18日、r184が4月16日、r185(0.185.0)が6月25日です。この7区間の平均は約51日で、1か月あまりで次が出た時期もあれば70日空いた時期もあり、直近3回は2か月前後で推移しています。破壊的変更はこの節目にまとめて入るため、バージョンを固定せずに運用すると数か月単位でビルドが壊れます。package.jsonでは^を付けず、"three": "0.185.1"のように完全固定するのが実務的です。
最新版r185とバージョン番号の読み方
npm上のバージョンは0.185.1、公式ドキュメントやコミュニティでの呼称はr185です。対応関係は単純で、マイナー番号がリビジョン番号にあたります。0.161.0ならr161です。
先頭が0.のまま止まっている点には意味があります。セマンティックバージョニングの慣習で0.x系は互換性を保証しない段階を指し、Three.jsは実際にマイナー更新でAPIを削除してきました。後述するUMDビルドの削除もexamples/js/の削除も、すべてマイナー更新で起きています。
import mapとnpm+Viteによる導入手順の選び分け
import mapでCDNから読み込む手順
ビルドツールを使わない場合、公式マニュアルが案内する方法はimport mapです。HTMLのhead内にthreeとthree/addons/の解決先を書き、本体はES Modulesとして読み込みます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Three.js r185</title>
<style>body { margin: 0; }</style>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
}
}
</script>
</head>
<body>
<script type="module" src="./main.js"></script>
</body>
</html>
バージョンを0.185.1のように具体的に書く点が要点です。指定を省いたthree@latestは、次のリリースが来た瞬間にAPI削除を巻き込みます。またthree/addons/の末尾スラッシュは必須で、これが無いとthree/addons/controls/OrbitControls.jsの解決に失敗します。
ローカルで開く際はfile://ではなく簡易サーバを立ててください。npx serve .で十分です。ES Modulesとテクスチャ読み込みはどちらもオリジンを要求するため、HTMLファイルをダブルクリックで開くと動きません。
npmとViteで開発環境を作る手順
公式マニュアルが「大半の利用者に推奨」と書いているのはこちらです。
npm install --save [email protected]
npm install --save-dev vite
npx vite
npx viteを実行するとhttp://localhost:5173が開き、import * as THREE from 'three'がそのまま解決されます。公開用のファイルはnpx vite buildでdist/に出力されます。TypeScriptで書く場合、型定義は本体に同梱されないため、npm install --save-dev @types/threeを別途入れます(0.185.4)。この型定義はコミュニティ管理のthree-types/three-ts-typesリポジトリから生成されています。
どちらを選ぶかの判断基準
本番運用のプロダクトでCDNのimport mapを使うのは避けてください。外部CDNの応答が初回描画の直列依存になるうえ、ツリーシェイキングが効かずbuild/three.module.jsを丸ごと転送することになります。
import mapが向くのは、検証用のスケッチ、社内向けの単発デモ、ビルド環境を用意できない既存CMSへの埋め込みの3つです。それ以外はnpmとViteを選んでください。
r128世代のコードが動かない原因と現行版への書き換え
日本語で「Three.js 使い方」を検索すると、three.min.jsをscript要素で読み込み、THREE.の名前空間からすべてを呼ぶ形のコードが多く出てきます。これはr128前後、2021年ごろの書き方です。現在は配布物の構成が変わっており、そのままでは動きません。どこがいつ消えたのかを整理します。
| 古い記事での書き方 | 状態 | r185での書き方 |
|---|---|---|
build/three.min.jsをscriptで読込 |
r161で削除 | import mapでbuild/three.module.js |
examples/js/controls/OrbitControls.js |
r148で削除 | three/addons/controls/OrbitControls.js |
new THREE.GLTFLoader() |
名前空間に非公開 | three/addons/loaders/GLTFLoader.jsからimport |
new THREE.Geometry() |
r125で削除 | BufferGeometry + setAttribute |
new THREE.Face3() |
r126で削除 | 頂点を3つずつ並べて属性に登録 |
new THREE.WebGL1Renderer() |
r163で削除 | WebGLRenderer(WebGL 2が前提) |
new THREE.Clock() |
r183で非推奨 | new THREE.Timer() |
削除の時期は配布物そのもので確認できます。r160のtarballにはbuild/three.jsとbuild/three.min.jsが入っていますが、r161のtarballにはthree.cjs、three.module.js、three.module.min.jsしかありません。examples/js/配下は0.147.0までHTTP 200で取得できるのに対し、0.148.0以降は404を返します。
厄介なのは、古い記事が指定するCDNのURL自体は今も生きている点です。cdnjsのr128のthree.min.jsはHTTP 200を返します。そのため読者は「ファイルは読めているのにエラーになる」状態に陥ります。r128をピン留めしたところで解決しません。r128のUMDビルドを読み込んで名前空間を検査すると、THREE.GeometryもTHREE.Face3もTHREE.GLTFLoaderもすべてundefinedです。古い記事のコードは、その記事が指定するバージョンでも動きません。
THREE.Geometryで頂点を組むコードの書き換え
頂点を直接指定するコードはBufferGeometryに置き換えます。vertices.pushとfaces.pushの代わりに、座標をFloat32Arrayへ平坦に並べ、3要素で1頂点として属性に登録します。
import * as THREE from 'three';
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
-1, 1, 0,
1, 1, 0,
1, -1, 0,
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.computeVertexNormals();
const mesh = new THREE.Mesh(
geometry,
new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide })
);
scene.add(mesh);
BufferAttributeの第2引数3は1頂点あたりの要素数です。面の向きを指定していないため、裏側から見ると消えます。side: THREE.DoubleSideを付けているのはそのためです。ライティングを効かせるマテリアルを使う場合はcomputeVertexNormals()が要ります。
Clockからの移行先となるTimerの使い方
経過時間の取得に長く使われてきたTHREE.Clockは、r183で非推奨になりました。配布物のソースには@deprecated, r183というコメントが付いており、インスタンス化すると「THREE.Clock: This module has been deprecated. Please use THREE.Timer instead.」という警告が出ます。後継のTimerはr179でコアのエクスポートに加わっており、r183でClockが非推奨になったことで正式な移行先になりました。
置き換えは1行です。new THREE.Clock()をnew THREE.Timer()に変え、毎フレームtimer.update(time)を呼んでからtimer.getDelta()を読みます。Clockが呼び出しのたびに時刻を測っていたのに対し、Timerはupdate()の時点で値を確定させるため、1フレーム内で複数回getDelta()を呼んでも同じ値が返ります。アニメーション更新と物理演算で別々のdeltaを使ってしまう事故が起きません。
最小構成のシーンを描画するコードと各行の役割
import mapを書いたHTMLから読み込むmain.jsの全体です。立方体を1つ置き、ライトを当てて回転させます。
import * as THREE from 'three';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111418);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(3, 2, 5);
camera.lookAt(new THREE.Vector3(0, 0, 0));
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const box = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x44aa88 })
);
scene.add(box);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const light = new THREE.DirectionalLight(0xffffff, 2.5);
light.position.set(3, 5, 4);
scene.add(light);
const timer = new THREE.Timer();
renderer.setAnimationLoop((time) => {
timer.update(time);
box.rotation.y += timer.getDelta() * 0.8;
renderer.render(scene, camera);
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
PerspectiveCameraの4つの引数は、視野角45度、アスペクト比、手前の描画境界0.1、奥の描画境界100です。手前の値を0.001のように極端に小さくすると深度バッファの精度が落ち、重なった面がちらつくZファイティングが出ます。0.1前後から始めてください。
座標を扱うVector3はposition、scale、ライトの向きなど広範に登場します。camera.position.set(3, 2, 5)はVector3への代入の短縮形で、camera.position.x = 3と書いても同じです。
setPixelRatioにMath.min(window.devicePixelRatio, 2)を渡しているのは、描画するピクセル数が比の2乗で増えるからです。devicePixelRatioが3のスマートフォンで無制限に追従させると、2で頭打ちにした場合の2.25倍のピクセルを毎フレーム塗ることになります。
ループはrequestAnimationFrameを直接書かずrenderer.setAnimationLoop()を使ってください。WebXRのセッション中はブラウザ側のフレーム供給元が切り替わるため、requestAnimationFrame直書きのループはヘッドセット上で描画されません。ブラウザXRの対応状況はWebXRのデモ・サンプルで試すブラウザXR入門にまとめています。
リサイズ時にcamera.updateProjectionMatrix()を忘れると、描画領域だけが伸びて映像が横に潰れます。aspectの書き換えは投影行列に自動反映されません。
glTFモデルの読み込みとアニメーション再生の実装
GLTFLoaderとOrbitControlsのaddons経由の読み込み
モデルローダーとカメラ操作は本体に含まれず、three/addons/配下にあります。古い記事のnew THREE.GLTFLoader()が動かないのはこのためです。THREE名前空間には最初から入っていません。
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
let mixer = null;
new GLTFLoader().load('/models/robot.glb', (gltf) => {
scene.add(gltf.scene);
if (gltf.animations.length > 0) {
mixer = new THREE.AnimationMixer(gltf.scene);
mixer.clipAction(gltf.animations[0]).play();
}
}, undefined, (error) => {
console.error('glTFの読み込みに失敗しました', error);
});
const timer = new THREE.Timer();
renderer.setAnimationLoop((time) => {
timer.update(time);
const delta = timer.getDelta();
if (mixer !== null) mixer.update(delta);
controls.update();
renderer.render(scene, camera);
});
読み込むファイルはバイナリ形式の.glbを推奨します。テキスト形式の.gltfはテクスチャやバイナリバッファを別ファイルとして参照するため、相対パスがずれると本体だけ読めてモデルが真っ黒になります。enableDampingを有効にしたなら、毎フレームcontrols.update()を呼んでください。呼び忘れると慣性が効かず、ドラッグを離した瞬間に動きが止まります。
AnimationMixerによるクリップ再生の手順
glTFに含まれるアニメーションはgltf.animationsに配列で入っています。AnimationMixerを対象オブジェクトに紐づけ、取り出したクリップをclipAction().play()で再生し、毎フレーム経過秒数をmixer.update(delta)に渡します。
再生位置は経過秒数の累積で決まります。0秒から1秒にかけてY座標が0から2へ動くクリップを再生し、0.5秒ぶん進めるとY座標は1.000になります。mixer.update()にミリ秒をそのまま渡すとクリップ長を大きく超え、既定のループ再生では再生位置が折り返します。同じ1秒のクリップに500を渡した場合、終端で止まるのではなく再生位置が0秒へ戻り、Y座標は0.000になります。Timer.getDelta()が返す秒単位の値をそのまま渡してください。
複数のクリップを持つモデルではgltf.animations[0]決め打ちを避け、THREE.AnimationClip.findByName(gltf.animations, 'Walk')のように名前で取得します。書き出しツールによって配列の順序が変わるためです。
バンドルサイズの実測値とツリーシェイキングの限界
Three.jsを採用してよいかを決める材料として、転送量は無視できません。three 0.185.1をesbuild 0.28.2で--bundle --minify --format=esmしてgzip -9した実測値が次の表です(2026年8月9日測定)。
| 構成 | minify後 | gzip後 |
|---|---|---|
最小シーンからWebGLRendererだけ外した構成 |
314,577 B | 72,686 B |
| 本記事の最小シーン一式 | 526,827 B | 133,141 B |
| 最小シーン+OrbitControls+GLTFLoader | 625,294 B | 159,017 B |
| import * as THREE を全参照 | 733,125 B | 189,014 B |
読み取れることは2つあります。第一に、名前付きインポートで必要な機能だけを取り出しても、全量参照との差はgzip後で189,014バイトから133,141バイトへの約30%減にとどまります。「ツリーシェイキングすれば軽くなる」という説明は、期待するほどの効果を生みません。WebGLRendererがシェーダのチャンクとマテリアル定義を広く参照しており、使っていないマテリアルのGLSL文字列まで残るためです。
第二に、描画器を外した構成が72,686バイトである一方、WebGLRendererを1つ足すだけで133,141バイトになります。増分の約60KBは、どの機能を削っても消せない固定費です。つまりThree.jsを使う限り、gzip後130KB前後がスタート地点になります。
この数字を踏まえた判断は明確です。装飾目的で背景に3Dを1つ置きたいだけなら、Three.jsは採用しないでください。CSSのグラデーションやCanvas 2D、あるいは用途を絞ったライブラリで足ります。React環境で3D風の背景表現だけが欲しい場合は、React・Next.jsで3Dグラデーション背景を実装する手順のように目的特化のパッケージを選ぶほうが転送量に見合います。130KBを払う価値があるのは、モデルの回転操作、製品コンフィギュレータ、3Dデータの可視化のように、3Dであること自体が機能になっている場合です。
削減の手段が無いわけではありません。glTFをDRACOLoaderで圧縮すればモデル側の転送量は落ちますし、3Dを表示する画面でだけ動的インポートすれば初期表示のバンドルからは外せます。ただしどちらもThree.js本体の固定費には効きません。
React Three FiberとWebGPURendererの採用条件
React Three Fiberのバージョン整合
ReactからThree.jsを使う場合、シーングラフをJSXで宣言できるReact Three Fiberが事実上の標準です。最新は2026年7月31日公開の@react-three/fiber 9.7.0で、peerDependenciesはreactが19以上19.3未満、threeが0.156以上と宣言されています。補助コンポーネント群の@react-three/dreiは10.7.8です。
ここで詰まりやすいのがReactの上限指定です。>=19 <19.3という範囲なので、Reactを先行して上げるとpeer依存の警告が出ます。Three.js側は下限のみの指定なので、r185との組み合わせに制約はありません。導入順としては、Reactのバージョンを先に確定させてからfiberとdreiを合わせてください。
素のThree.jsとReact Three Fiberのどちらを使うかは、状態管理の所在で決めます。3D側の状態がReactのstateと連動する画面ならReact Three Fiber、読み込んで回すだけの独立したビューアなら素のThree.jsで十分です。後者にReact Three Fiberを入れると、再レンダリングとフレームループの関係を調整する手間だけが増えます。
WebGPURendererに切り替える条件
r185の配布物にはthree/webgpuというエントリポイントがあり、WebGPURendererとノードベースのマテリアル記述(TSL)が含まれます。エントリポイントのthree/webgpuはbuild/three.webgpu.jsを指し、そのminify版build/three.webgpu.min.jsは667,861バイトで、three.module.min.jsの365,552バイトの約1.8倍になります。
今の時点で既存プロジェクトをWebGPURendererへ全面移行するのは勧めません。マテリアルやポストプロセスの記述がTSLベースに変わり、addons側の対応状況も機能ごとに差があるためです。切り替える価値があるのは、コンピュートシェーダでパーティクルを数十万単位で動かすなど、WebGLでは実装自体が成立しない処理を扱う場合に限られます。通常の3DビューアであればWebGLRendererのままで問題ありません。
よくある質問
Three.jsの読み方は?
「スリージェイエス」と読みます。公式サイトの表記はthree.jsで小文字ですが、文中では「Three.js」と先頭を大文字にする書き方が定着しています。npmのパッケージ名はthreeのみです。threejsは存在しませんが、three.jsという別パッケージが実在します。こちらは2016年で更新の止まった0.77.1で、説明文にも「’three.js’ npm package has been deprecated in favor of the ‘three’ npm package.」とあります。打ち間違えてもエラーにならず10年前のコードが入ってしまうため、npm install threeと正確に指定してください。GitHubのリポジトリ名はmrdoob/three.jsです。
Three.jsはどこからダウンロードすればよいですか?
自前のサーバに置く場合の入手元は、npmとGitHubのリリースページの2つです。ファイルを自前のサーバに置きたい場合はnpm pack [email protected]で取得し、package/build/three.module.jsとpackage/examples/jsm/を配置します。build/three.min.jsを探しても見つかりません。UMD形式のビルドはr161で配布物から削除されており、r160が最後の収録版です。自前配置の場合もimport mapで解決先を書く必要があります。
Three.jsは商用利用できますか?
できます。配布物に同梱されるLICENSEはMITライセンスで、著作権表記は「Copyright 2010-2026 three.js authors」です。求められるのは著作権表示とライセンス文をソフトウェアの複製に含めることだけで、利用料、ソースコードの公開、改変内容の開示はいずれも義務付けられていません。商用製品への組み込みや改変後の再配布も可能です。
Three.jsのAPIリファレンスはどこを見ればよいですか?
公式ドキュメントのthreejs.org/docs/が一次情報で、日本語のマニュアルはthreejs.org/manual/#ja/fundamentalsから辿れます(threejs.org/manual/ja/のディレクトリ直打ちは404です)。動く実例を探す場合は公式サンプル集のthreejs.org/examples/が早く、各サンプルはページ内でソースを表示できます。生成AIに読ませる用途では、r183でGitHubリポジトリにllms.txtが追加され、threejs.org/docs/llms.txtとthreejs.org/docs/llms-full.txtが公開されています(npmの配布物には同梱されません)。個別クラスの挙動を確定させたい場合は、npmで取得したpackage/build/three.module.jsを直接読むのが確実です。
Three.jsで3D Gaussian Splattingは扱えますか?
r185の時点では本体に専用の描画機能はありません。threeとthree/webgpuのエクスポートを検査しても、Gaussian Splatting向けのクラスは含まれていません。点群データの入れ物として使われる.ply形式のローダー(three/addons/loaders/PLYLoader.js)は同梱されているため、読み込みまでは標準機能で可能です。描画部分はコミュニティ製のライブラリを組み合わせる構成になります。