three.js LoaderUtils 深度指南:extractUrlBase 与 resolveURL 的源码解析与实战用法
three.js 的 LoaderUtils 是一个只提供静态方法的纯工具类,负责在资源加载体系中完成两类高频字符串操作:从文件 URL 中剥离出基准目录、以及把外部资源(纹理、二进制缓冲等)的相对引用解析为最终可请求的完整 URL。在 WebGL/WebGPU 场景中加载 glTF、OBJ、JSON 模型时,LoaderUtils 始终在幕后承担资源定位的职责。读完本文,你将掌握这两个工具方法的确切语义、边界行为,以及它们在 ObjectLoader、GLTFLoader 等真实加载器中的调用方式。
定位与角色:加载器体系中的静态工具箱
在 three.js 的源码布局中,所有内置加载器集中存放于 src/loaders 目录,LoaderUtils 即为其中之一,但与 Loader、FileLoader、ObjectLoader 等类实例不同,它不需要 new 出对象,也不挂接 LoadingManager,而是以静态方法的形式暴露两个纯函数工具:
extractUrlBase( url ):取 URL 的目录前缀;resolveURL( url, path ):把相对 URL 解析到给定基准路径上。
它的典型协作对象包括:
| 加载器 | 源码位置 | 协作方式 |
|---|---|---|
| ObjectLoader(核心 JSON 场景加载器) | src/loaders/ObjectLoader.js | 用 extractUrlBase 推导资源基准目录 |
| GLTFLoader(glTF 格式加载器) | examples/jsm/loaders/GLTFLoader.js | 用 extractUrlBase + resolveURL 推导 resourcePath |
| GLTFLoader 内部缓冲/图片加载 | examples/jsm/loaders/GLTFLoader.js | 用 resolveURL 解析 bufferDef.uri 与纹理源 URI |
Constructor:new LoaderUtils()
文档中声明了构造器 new LoaderUtils()。查看 源码类定义 可知,该类的构造器为空实现,且两个方法全部为 static。实际使用中你并不需要实例化它——直接通过 LoaderUtils.extractUrlBase(...) / LoaderUtils.resolveURL(...) 访问即可;刻意调用构造器只会得到一个空对象,没有任何内部状态。
静态方法一:.extractUrlBase( url : string ) : string
语义与实现
该方法“从给定 URL 中提取基准 URL(基目录)”。核心实现位于 src/loaders/LoaderUtils.js:
static extractUrlBase( url ) {
const index = url.lastIndexOf( '/' );
if ( index === - 1 ) return './';
return url.slice( 0, index + 1 );
}
逻辑非常直白:找到 URL 中最后一个 / 的位置,返回它(含)之前的全部子串。规则可归纳为:
- 若 URL 中不存在任何
/(即裸文件名),返回'./'(当前目录); - 否则返回最后一个
/之前(含该/)的子串。
行为示例(与单测一一对应)
test/unit/src/loaders/LoaderUtils.tests.js 中用 QUnit 固化了以下断言:
| 调用 | 返回 | 说明 |
|---|---|---|
extractUrlBase( '/path/to/model.glb' ) |
'/path/to/' |
保留尾部斜杠 |
extractUrlBase( 'model.glb' ) |
'./' |
裸文件名,无任何 / |
extractUrlBase( '/model.glb' ) |
'/' |
位于根目录,取到根斜杠 |
注意返回值始终以 /(或 ./)结尾,因此下游可直接把返回字符串与相对资源路径做拼接。
典型调用点:ObjectLoader 的目录推导
在 ObjectLoader.load 中,加载器通过如下语句为 JSON 场景内引用的外部资源推导基准路径:
const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path;
this.resourcePath = this.resourcePath || path;
其含义是:当用户没有通过 loader.setPath( ... ) 显式指定基准目录时,就以“被加载 JSON 文件自身所在的目录”作为后续纹理、几何体缓冲等资源的解析起点;只有显式设置了 path 才覆盖该自动推导。第二个加载入口(约在 ObjectLoader.js)重复了相同模式,保证从 parse 或直接 load 两条路径进入时行为一致。而 Loader.setPath 的定义位于基类 src/loaders/Loader.js,默认值为空字符串 ''(见 Loader.js)。
静态方法二:.resolveURL( url : string, path : string ) : string
语义与实现
该方法“将相对 URL 依据给定 path 解析为绝对地址;绝对路径、data URI 与 blob URL 原样返回;非法 URL 返回空字符串”。完整实现见 src/loaders/LoaderUtils.js:
static resolveURL( url, path ) {
// Invalid URL
if ( typeof url !== 'string' || url === '' ) return '';
// Host Relative URL
if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
}
// Absolute URL http://,https://,//
if ( /^(https?:)?\/\//i.test( url ) ) return url;
// Data URI
if ( /^data:.*,.*$/i.test( url ) ) return url;
// Blob URL
if ( /^blob:.*$/i.test( url ) ) return url;
// Relative URL
return path + url;
}
逐分支规则解读
该方法按顺序做六类判断,命中即返回,因此顺序本身也代表优先级:
- 非法输入:
url不是字符串或为空字符串,直接返回''。这是防止拼接出无意义地址的兜底分支。 - 主机相对路径(Host Relative):当
path以http://或https://开头、而url以/开头时,先把path裁到“协议 + 主机名”为止(正则(^https?:\/\/[^\/]+).*保留http(s)://host部分),使后续拼接得到主机级绝对 URL。 - 绝对 URL:
url以http://、https://或以//开头的协议相对地址时,原样返回,不与path拼接。 - Data URI:
url形如data:...(以逗号分隔 MIME 与内容),原样返回,便于加载器直接吃内嵌数据。 - Blob URL:
url以blob:开头时原样返回,支持URL.createObjectURL生成的地址。 - 其余一律视为相对 URL:执行
path + url的纯字符串拼接。
一个值得注意的工程细节:这整套逻辑是纯字符串运算,并不依赖浏览器 location 或 URL/base 标签,因此同样适用于 Web Worker、非 DOM 环境下的加载流程,行为完全可预期。
典型调用点:GLTFLoader 的 resourcePath 推导
glTF 是一种“单文件 + 外部缓冲/纹理引用”的格式,资源定位逻辑集中在 GLTFLoader.load:
if ( this.resourcePath !== '' ) {
resourcePath = this.resourcePath;
} else if ( this.path !== '' ) {
// 例:path = 'https://my-cdn-server.com/',url = 'assets/models/model.gltf'
// resourcePath = 'https://my-cdn-server.com/assets/models/'
// 引用 'model.bin' 将加载自 'https://my-cdn-server.com/assets/models/model.bin'
const relativeUrl = LoaderUtils.extractUrlBase( url );
resourcePath = LoaderUtils.resolveURL( relativeUrl, this.path );
} else {
resourcePath = LoaderUtils.extractUrlBase( url );
}
该处把本文两个工具方法组合使用:先用 extractUrlBase( url ) 取出 glTF 文件所在相对目录(如 assets/models/),再交给 resolveURL 与 CDN 基准 path 合并,最终使 glTF 内声明的 model.bin、../textures/texture.png 等相对引用(原注释明确展示可跨目录回退)能命中正确地址。随后在解析缓冲与纹理时再次使用:
- GLTFLoader.js:
loader.load( LoaderUtils.resolveURL( bufferDef.uri, options.path ), ... ),解析 glTFbuffers[].uri; - GLTFLoader.js:
LoaderUtils.resolveURL( sourceURI, options.path ),解析图片源sourceURI。
从中可以确认:path 在 GLTFLoader 内部即作为所有外部资源引用的解析基准,这也是官方文档中 resolveURL 第二参数名取 path 的原因。
与 LoadingManager.resolveURL 的区别(避免混淆)
在阅读源码时会发现 LoadingManager 也定义了一个同名方法 resolveURL,但它与 LoaderUtils.resolveURL 无关,二者勿混:
LoadingManager.resolveURL是在发起网络请求前执行的一层钩子:先把 URL 做String.normalize( 'NFC' )(保证含非 ASCII 字符的 URI 能被正确按 RFC 3987 百分号编码),再交给setURLModifier设置的回调改写地址;- 它由 FileLoader、ImageLoader、ImageBitmapLoader、ObjectLoader 等在真正发起请求前调用,常用于从 ZIP/拖放文件/Data URI 中改写资源来源;
- 而
LoaderUtils.resolveURL是纯静态字符串解析,不触碰任何请求或回调。
两者在管道中的位置也不同:LoaderUtils 负责“把相对路径算成完整路径”,LoadingManager 负责“在请求发出前做最终改写”。
快速参考表
| 方法 | 签名 | 典型返回值 |
|---|---|---|
extractUrlBase |
( url : string ) : string |
最后一个 / 之前的子串;无 / 时返回 './' |
resolveURL |
( url : string, path : string ) : string |
相对输入返回 path + url;绝对/data/blob 原样返回;非法输入返回 '' |
小结
LoaderUtils 是 three.js 资源加载链路里不起眼但不可或缺的静态工具类。extractUrlBase 用一次 lastIndexOf('/') 完成“目录剥离”,resolveURL 用一组有序的正则分支完成“相对/绝对/内嵌资源”的判别与拼接。理解它们后,无论阅读 src/loaders/ObjectLoader.js 的目录回退逻辑、还是排查 GLTFLoader 中 CDN 资源 404 的根因,都能更快定位:这类问题十有八九出在 path/resourcePath 是否按预期进入了 extractUrlBase 与 resolveURL 的输入。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00