引入 three.js
本教程共 40 篇 · 第 3 篇 · 更新于 2026-08-14 · 约 8 分钟阅读
本节目标:学会用现代方式(importmap + ESM)把 three.js 引入页面,并能区分新旧两种引入方式的差别。学完你能写出一个能跑通的最小 three.js 页面。
引入方式已经变了
网上老教程最常见的写法是:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
这是 r150 起弃用、r160 移除的「全局变量」写法:加载完,页面里就多了个 window.THREE,所有类都挂在它下面。但从 r150 起官方弃用了这种构建,r160 起直接删掉了 build/three.js 和 build/three.min.js 这两个文件。现在官方推荐的唯一方式是 ES Module + importmap。
Note想抄老教程代码的同学注意:r160 之后,全局 script 方式已经拿不到 THREE 对象了。认准 importmap 写法。
先认识 ES Module
ES Module(简称 ESM)是 JavaScript 官方的模块系统,2015 年随 ES6 发布。一个文件就是一个模块,用 export 导出,用 import 引入。浏览器里使用模块,要在 script 标签上加 type=“module”:
<script type="module">
import ... from '...';
</script>
type=“module” 的脚本有两个特点:自动延迟执行(等页面解析完再跑,相当于自带 defer);运行在严格模式下,写错变量名会直接报错。这些特点让模块代码比普通脚本更安全、更可预测。模块脚本之间可以互相 import,three.js 的 addons 内部就是这么组织的。
three.js 从 r160 起只提供 ESM 构建(build/three.module.js),所以我们的代码必须走 ESM。
importmap 是什么
importmap 是一段 JSON 配置,告诉浏览器:当代码里写 import ‘three’ 时,去哪个 URL 加载。它解决了裸模块名(不带路径的包名)在浏览器里无法解析的问题——浏览器原生只认相对路径和完整 URL,不认 ‘three’ 这种包名。
官方的 importmap 配置长这样,本教程全书统一使用:
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
}
}
</script>
两行配置各有用处:
- “three” 映射核心库
- “three/addons/” 映射官方扩展(比如 OrbitControls 轨道控制器),后面的章节会用到
importmap 从 2023 年起被所有主流浏览器支持,包括 Safari 16.4+,可以放心用。它带来的好处是:版本只在一处写,全页面的 import 语句保持干净。
另外,importmap 是严格的 JSON:键和值都要双引号,最后一项不能有逗号。格式写错的话,浏览器会忽略整个映射,页面上所有 import 全部失败,控制台报 Failed to resolve module specifier。所以每次改 importmap,先检查一遍 JSON 格式。importmap 放在 head 里或 body 前面都行,只要在使用它的模块脚本之前。
第一个能跑的最小页面
把下面代码保存为 index.html,用本地服务器打开(第 2 章讲过,别用 file://),控制台会打印 three.js 版本号:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>引入 three.js</title>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
}
}
</script>
</head>
<body>
<script type="module">
import * as THREE from 'three';
console.log('three.js 版本:r' + THREE.REVISION);
</script>
</body>
</html>
控制台输出 three.js 版本:r185 就说明引入成功。也可以在 Network 面板里确认 three.module.js 的请求返回 200。这一步值得亲手做一遍:它验证了 importmap、ESM、本地服务器整条链路都正常,后面所有章节的示例都建立在这条链路上。
为什么用 CDN
CDN(内容分发网络)把文件缓存在全球各地的服务器上,用户访问时从最近的节点加载,速度快、不占你服务器的带宽。three.js 官方推荐的 CDN 有 jsdelivr、unpkg 等,本教程统一用 jsdelivr。
用 CDN 必须锁定版本:URL 里的 @0.185.0 就是版本号。如果不写版本,CDN 会返回最新版,而 three.js 每月发布一版,API 随时可能变,今天能跑的代码下个月可能就报错。锁定版本是写 three.js 的第一条纪律。
怎么确认加载成功?打开 Network 面板,过滤 JS,能看到 three.module.js 返回 200;控制台没有红色报错,说明引入链路是通的。
两种 import 写法
ESM 支持两种引入方式。
方式一:命名空间导入,本书统一使用:
import * as THREE from 'three';
const scene = new THREE.Scene();
方式二:具名导入:
import { Scene, PerspectiveCamera, WebGLRenderer } from 'three';
const scene = new Scene();
两种写法效果一样。本书统一用命名空间写法,理由有两个:新手不用记每个类从哪来;代码和官方文档示例对得上。
细心的读者会发现:import ‘three’ 不带后缀,import ‘three/addons/controls/OrbitControls.js’ 却带着。原因在映射方式不同:‘three’ 直接映射到一个具体文件;‘three/addons/’ 映射的是一个目录前缀,剩下的路径必须自己写全,所以 .js 后缀不能省。
引入官方扩展:three/addons/
three.js 的扩展(控制器、加载器、后期处理等)不在核心库里,路径统一是 three/addons/。比如轨道控制器:
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
console.log(OrbitControls); // 输出类,不是 undefined
</script>
注意两点:扩展文件名带 .js 后缀;核心库和扩展必须用同一个版本,混用版本会出现奇怪的报错。
Note老教程里的 THREE.OrbitControls、THREE.GLTFLoader 写法已经不存在了。扩展一律从 three/addons/ 路径导入。
用 npm 安装(可选)
如果你用 Node.js 生态(Vite、Webpack 等构建工具),可以装 npm 包:
npm install three@0.185.0
代码里的写法不变:
import * as THREE from 'three';
构建工具会帮你解析包名、打包代码。npm 方式适合正式项目;本教程的示例统一用 importmap + CDN,因为它在浏览器里零配置直接跑,最适合学习。
怎么选?一句话:学习用 importmap + CDN,正式项目用 npm。等项目需要打包压缩、按需加载时,再迁移到 npm 方式也不迟——两种方式的 import 写法几乎一样,迁移成本很低。
离线场景:下载到本地
如果项目要求离线运行(内网部署、断网演示),可以把文件下载到本地,用相对路径引入:
# 在项目目录下创建 vendor 目录,放入 three.module.js
curl -o vendor/three.module.js https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js
<script type="importmap">
{
"imports": {
"three": "./vendor/three.module.js",
"three/addons/": "./vendor/addons/"
}
}
</script>
扩展文件同理,按 examples/jsm/ 的目录结构放到 vendor/addons/ 下。日常学习用不上这个方案,但知道有这条路,遇到内网环境不慌。
常见坑
- importmap 必须写在所有使用 import 的 script 之前
- 一个页面只能有一个 importmap,多个要合并成一个
- 忘记 type=“importmap” 或 type=“module”,浏览器会报错或什么都不发生
- file:// 打开会报 CORS 错误,务必用本地服务器
- 版本号写错(比如 @0.185 少写一位),CDN 返回 404,页面静默失败
- 扩展路径写错(比如漏了 .js 后缀):模块加载 404,控制台有报错,按提示核对路径
- 混用不同版本的 three:核心库和 addons 版本不一致会出现诡异报错,全部统一到 @0.185.0
Tip调试引入问题时,最有效的方法是打开控制台看报错。看到 “Failed to resolve module specifier” 就检查 importmap 里的 URL 有没有写错。
引入成功,下一节画出第一个 3D 场景。