three.js AnimationLoader 详解:加载 JSON 动画剪辑文件的完整指南
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
AnimationLoader是 three.js 中专门用于加载 JSON 格式动画剪辑(AnimationClip)数组的加载器。它继承自通用基类Loader,底层通过FileLoader发起网络请求,将文本反序列化后交给AnimationClip.parse转换为可被AnimationMixer直接播放的动画剪辑数组。读完后你将掌握其构造参数、load/loadAsync/parse的完整用法、JSON 文件的数据结构,以及出错时的异常传播机制。
一、类定位与继承关系
AnimationLoader的职责单一而明确:加载以 JSON 数组形式组织的动画剪辑。文档中给出的官方定位是(docs/pages/AnimationLoader.html.md):
Class for loading animation clips in the JSON format. The files are internally loaded via FileLoader.
从源码 src/loaders/AnimationLoader.js 可以确认其继承链与依赖:
import { AnimationClip } from '../animation/AnimationClip.js'; import { FileLoader } from './FileLoader.js'; import { Loader } from './Loader.js'; class AnimationLoader extends Loader { // ... }- 它继承自 Loader(抽象基类),因此自动获得
loadAsync、setPath、setCrossOrigin、setRequestHeader等全部通用能力; - 实际的文件读取由 FileLoader 完成,
AnimationLoader本身不直接操作XMLHttpRequest; - 解析环节完全委托给 AnimationClip 的静态方法
AnimationClip.parse(见 src/animation/AnimationClip.js)。
单元测试 test/unit/src/loaders/AnimationLoader.tests.js 也显式断言了new AnimationLoader() instanceof Loader === true,印证了这一继承关系。
二、快速上手:加载与异步加载
文档给出的最小可用示例如下(注意示例中文件应为合法的 JSON 内容):
const loader = new THREE.AnimationLoader(); const animations = await loader.loadAsync( 'animations/animation.json' );2.1 构造函数
new AnimationLoader( manager : LoadingManager )manager:加载管理器,用于统一跟踪一批资源的加载进度与错误。源码中的构造函数只有一行:
constructor( manager ) { super( manager ); }如果不传manager,Loader 基类会回退到全局默认的DefaultLoadingManager(this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;)。
2.2 load 回调式 API
### .load( url : string, onLoad : function, onProgress : onProgressCallback, onError : onErrorCallback )| 参数 | 说明 |
|---|---|
| url | 要加载文件的路径/URL,也可以是一个 data URI |
| onLoad | 加载完成后执行,参数为Array<AnimationClip> |
| onProgress | 加载进行中执行,参数为ProgressEvent |
| onError | 发生错误时执行,参数为Error |
loadAsync则由基类 Loader 提供,本质是把load包装成 Promise:
loadAsync( url, onProgress ) { const scope = this; return new Promise( function ( resolve, reject ) { scope.load( url, resolve, onProgress, reject ); } ); }因此onLoad对应resolve、onError对应reject,两种 API 行为完全一致,可根据项目的异步风格任选其一。
三、load 内部流程:源码级拆解
对照 src/loaders/AnimationLoader.js 的load实现,其完整调用链为:
load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); loader.load( url, function ( text ) { try { onLoad( scope.parse( JSON.parse( text ) ) ); } catch ( e ) { if ( onError ) { onError( e ); } else { error( e ); } scope.manager.itemError( url ); } }, onProgress, onError ); }可以归纳出几个关键点:
- 继承基类配置:每次
load都会新建一个FileLoader,并把当前AnimationLoader实例上的path、requestHeader、withCredentials同步过去。也就是说,loader.setPath( 'data/' )、loader.setRequestHeader( { ... } )等基类 setter 的修改会在真正发请求前生效; - 解析发生在回调内部:
JSON.parse( text )与scope.parse( ... )都被包在try/catch中——即使网络请求本身成功,只要 JSON 不合法或字段缺失导致解析抛错,也会走错误分支; - 错误的双通道上报:
- 若调用方提供了
onError,错误交给onError( e );否则调用内部error( e )(即控制台输出); - 无论哪种情况,都会调用
scope.manager.itemError( url ),让LoadingManager记录该 URL 出错。这意味着如果你用LoadingManager的onError回调做统一错误处理,AnimationLoader 的解析失败同样会被捕获到。
- 若调用方提供了
四、parse:从 JSON 对象到 AnimationClip 数组
4.1 方法签名
### .parse( json : Object ) : Array.<AnimationClip>json:序列化的动画剪辑;返回值:解析得到的AnimationClip实例数组。
parse的实现非常直白(src/loaders/AnimationLoader.js):
parse( json ) { const animations = []; for ( let i = 0; i < json.length; i ++ ) { const clip = AnimationClip.parse( json[ i ] ); animations.push( clip ); } return animations; }由此可以确认JSON 文件顶层必须是一个数组,每个元素是一个动画剪辑的序列化对象;parse本身是同步的,因此也可以绕过网络直接调用loader.parse( myJson )来解析本地数据。
4.2 JSON 数据结构详解
真正的字段解析发生在 AnimationClip.parse:
static parse( json ) { const tracks = [], jsonTracks = json.tracks, frameTime = 1.0 / ( json.fps || 1.0 ); for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); } const clip = new this( json.name, json.duration, tracks, json.blendMode ); clip.uuid = json.uuid; clip.userData = JSON.parse( json.userData || '{}' ); return clip; }结合此实现与静态toJSON,一个合法的 AnimationLoader JSON 文件结构如下:
[ { "name": "Walk", "duration": 2.5, "fps": 1, "blendMode": 0, "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "userData": "{\"loopCount\":3}", "tracks": [ { "name": ".position", "type": "VectorKeyframeTrack", "times": [ 0, 1, 2 ], "values": [ 0, 0, 0, 0, 1, 0, 0, 2, 0 ] }, { "name": ".quaternion", "type": "QuaternionKeyframeTrack", "times": [ 0, 1 ], "values": [ 0, 0, 0, 1, 0, 0, 0.7071, 0.7071 ] } ] } ]各字段的含义与实现依据:
- fps:
frameTime = 1.0 / ( json.fps || 1.0 ),随后每条轨道被scale( frameTime )缩放。即若 JSON 中的times记录的是"帧序号",fps用于把帧时间换算成秒;缺省fps为 1,times 即按秒处理。这是很多人误以为动画时长不对的根源; - blendMode:对应剪辑的
blendMode属性(NormalAnimationBlendMode/AdditiveAnimationBlendMode),决定多动画混合时的叠加方式; - userData:序列化时被
JSON.stringify成字符串存储,解析时再用JSON.parse( json.userData || '{}' )还原,因此 JSON 中它是一个字符串而非对象; - uuid:
clip.uuid = json.uuid直接覆盖自动生成的 UUID,保证序列化往返后标识稳定。
4.3 轨道类型映射
每条轨道通过json.type字符串决定具体类型,映射逻辑见 src/animation/AnimationClip.js 中的getTrackTypeForValueTypeName:
JSONtype(不区分大小写) | 生成的轨道类 |
|---|---|
scalar/double/float/number/integer | NumberKeyframeTrack |
vector/vector2/vector3/vector4 | VectorKeyframeTrack |
color | ColorKeyframeTrack |
quaternion | QuaternionKeyframeTrack |
bool/boolean | BooleanKeyframeTrack |
string | StringKeyframeTrack |
两条硬约束来自parseKeyframeTrack:
json.type为undefined时直接抛出THREE.KeyframeTrack: track type undefined, can not parse;- 类型名不在上表时抛出
Unsupported typeName错误。
这两个异常都会沿load中的try/catch冒泡到onError——编写或校验动画 JSON 时应重点检查tracks[].type与tracks[].name是否齐全。
4.4 keys 与 times/values 两种轨道表示
parseKeyframeTrack中还兼容了"keys 数组"这一早期表示方式:
if ( json.times === undefined ) { const times = [], values = []; AnimationUtils.flattenJSON( json.keys, times, values, 'value' ); json.times = times; json.values = values; }也就是说,若轨道中没有显式times/values,但有形如keys: [ { time: 0, value: ... }, ... ]的数组,AnimationUtils.flattenJSON会将其拍平为times与values后再构造轨道。新文件推荐直接使用times+values的扁平格式。
五、基类 Loader 提供的可配置项
由于AnimationLoader继承 Loader,以下实例属性与 setter 均可用(默认值来自源码注释):
| 属性 / 方法 | 默认值 | 说明 |
|---|---|---|
manager | DefaultLoadingManager | 加载管理器 |
crossOrigin | 'anonymous' | CORS 模式,跨域加载时生效 |
withCredentials | false | XMLHttpRequest.withCredentials,本地或同源加载时无效 |
path | '' | 资源基础路径,可配合setPath( path )设置 |
resourcePath | '' | 附属资源的基础路径 |
requestHeader | {} | 自定义 HTTP 请求头,可配合setRequestHeader( ... ) |
loadAsync( url, onProgress ) | — | Promise 版load |
abort() | — | 中止进行中的请求 |
跨域加载动画文件时的典型写法:
const loader = new THREE.AnimationLoader(); loader.setCrossOrigin( 'anonymous' ); // 或 'include' loader.setPath( 'assets/animations/' ); loader.setRequestHeader( { 'Authorization': 'Bearer xxx' } ); loader.load( 'walk.json', ( animations ) => { mixer.clipAction( animations[ 0 ] ).play(); } );注意:
AnimationLoader.load中把path、requestHeader、withCredentials复制给了新建的FileLoader;crossOrigin同理由FileLoader依据manager体系处理。所有 setter 都返回this,可链式调用。
六、加载后如何使用:接入 AnimationMixer
load的onLoad拿到的animations是AnimationClip[],通常按名称查找后交给AnimationMixer播放:
const loader = new THREE.AnimationLoader(); loader.load( 'animations/walk.json', ( animations ) => { const clip = THREE.AnimationClip.findByName( animations, 'Walk' ) || animations[ 0 ]; const action = mixer.clipAction( clip ); action.play(); } );AnimationClip.findByName(见 src/animation/AnimationClip.js)支持直接在剪辑数组中按name检索,找不到时返回null,因此常作为安全查找手段。
七、与 ObjectLoader 的对比
在 three.js 的加载器体系中,AnimationClip.parse还被 ObjectLoader 复用——当加载的 JSON 模型中内嵌animations字段时,ObjectLoader 内部同样调用AnimationClip.parse( data )生成剪辑。两者的分工可以这样理解:
- AnimationLoader:面向独立的、纯动画剪辑的 JSON 文件(顶层数组),产出
AnimationClip[]; - ObjectLoader:面向完整的场景 JSON(几何体、材质、网格 + 内嵌动画),产出
Group,动画只是其中一部分。
如果动画与几何体绑定在一起,用ObjectLoader一次取回更省事;如果只需要动画数据(例如运行时按状态机加载、切换剪辑),AnimationLoader更轻量、职责更清晰。
八、常见陷阱与错误处理小结
- 顶层必须是数组:
parse遍历json.length,传入单个对象会得到空数组或异常; fps决定时间换算:times是帧序号时务必提供fps,否则frameTime按 1 处理,动画时长会被放大或压缩;type必填:轨道缺少type字段会抛错,且错误发生在解析阶段而非网络阶段;userData是字符串:写入 JSON 时需要JSON.stringify,读取后才是对象;- 错误统一上报:解析错误会触发
manager.itemError( url ),配合LoadingManager的onError回调可以实现全局错误兜底; - 文件名扩展名:官方文档示例写作
'animations/animation.js',实际文件内容是纯 JSON,生产环境建议使用.json扩展名以便浏览器与服务器返回正确 MIME 类型。
九、相关源码与文档索引
| 资源 | 路径 |
|---|---|
| AnimationLoader 源码 | src/loaders/AnimationLoader.js |
| Loader 基类(loadAsync / 配置项) | src/loaders/Loader.js |
| FileLoader(底层请求) | src/loaders/FileLoader.js |
| AnimationClip.parse / findByName | src/animation/AnimationClip.js |
| 单元测试 | test/unit/src/loaders/AnimationLoader.tests.js |
| 官方文档页 | docs/pages/AnimationLoader.html.md、docs/pages/Loader.html.md、docs/pages/FileLoader.html.md、docs/pages/AnimationClip.html.md |
综上,AnimationLoader虽然实现精简(构造、load、parse 三个方法),但它把"网络加载 — JSON 反序列化 — 轨道类型分发 — 帧时间缩放 — 错误上报"整条链路组织得非常完整。理解了fps时间缩放与轨道类型映射这两个细节,基本可以覆盖绝大多数 JSON 动画文件加载与播放的实战场景。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考