首页 / Three.js 入门教程 / 引入 three.js

Three.js 入门教程

引入 three.js

本教程共 40 篇 · 第 3 篇 · 更新于 2026-08-14 · 约 8 分钟阅读

Three.jsESMimportmapCDNnpmJavaScript 模块

本节目标:学会用现代方式(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 场景。