「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」
示例代码采用three.js-r73版本: cdnjs.cloudflare.com/ajax/libs/t…
我们已经实战了VTKLoader来加载我们的VTK模型,那么VTKLoader内部是怎么实现的呢,我们来一探究竟。
VTKLoader构造函数
THREE.VTKLoader = function (manager) {
this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
};
THREE.VTKLoader.prototype = {
constructor: THREE.VTKLoader,
}
THREE.VTKLoader的原型上设置了构造函数,- 可以传入
manager参数,这个参数可以自定义加载管理器 - 如果没有传,就是THREE内部默认的加载管理器
THREE.DefaultLoadingManager
- 可以传入
THREE.DefaultLoadingManager
- 我们来看下默认加载管理器做了什么事
THREE.DefaultLoadingManager = new THREE.LoadingManager();
- 默认加载管理器是实例化的
THREE.LoadingManager
THREE.LoadingManager
- 这个函数中定义了一些变量
THREE.LoadingManager = function (onLoad, onProgress, onError) {
// 外层 this 别名
var scope = this;
var isLoading = false,
itemsLoaded = 0, // 代表已经加载字节数
itemsTotal = 0; // 代表加载字节总数
this.onStart = undefined; // 初始化时这个函数没有定义,需要重写
this.onLoad = onLoad; // 加载完成的回调
this.onProgress = onProgress; // 加载进度的回调
this.onError = onError; // 加载错误的回调
....
}
- 需要注意的是
itemsLoaded和itemsTotal记录的是字节数 - 整个函数的生命周期是这样的
- itemStart 加载之前
- onStart 加载载开始的回调
- itemEnd 加载结束
- onLoad 加载完成的回调
itemStart 和 itemEnd 在每次服务器返回数据时调用
// 加载 url 地址
this.itemStart = function (url) {
// 总共加载字节数
itemsTotal++;
// 如果没有加载
if (isLoading === false) {
//
if (scope.onStart !== undefined) {
// url 已经加载的数量 总共加载数量
scope.onStart(url, itemsLoaded, itemsTotal);
}
}
// 设置为正在加载
isLoading = true;
};
- itemStart每次调用会记录总共的加载字节数的总数
- 如果没有加载,并且有onStart回调,就调用这个方法
- 把记载状态记为加载中
this.itemEnd = function (url) {
// 已经加载字节数
itemsLoaded++;
if (scope.onProgress !== undefined) {
scope.onProgress(url, itemsLoaded, itemsTotal);
}
// 已经加载的字节数等于总的字节数
if (itemsLoaded === itemsTotal) {
isLoading = false;
if (scope.onLoad !== undefined) {
// 调用onLoad
scope.onLoad();
}
}
};
- itemEnd 会记录已经加载的字节数
- 如果定义了
onProgress回调函数,就调用 - 如果已经加载的字节数等于了总的字节数,说明加载完成,调用
onLoad回调函数
VTKLoader的load方法
- 介绍完了构造函数,我们来看看VTKLoader的load方法
/**
*
* @param {*} url url地址
* @param {*} onLoad 加载成功回调
* @param {*} onProgress 加载过程回调
* @param {*} onError 加载错误回调
*/
load: function (url, onLoad, onProgress, onError) {
var scope = this;
// 调用THREE的XHR请求实例,传入了加载管理器
var loader = new THREE.XHRLoader(scope.manager);
// 设置cross
loader.setCrossOrigin(this.crossOrigin);
// 调用加载函数,返回文本数据
loader.load(url, function (text) {
// 通过parse解析文本数据,返回geometry
onLoad(scope.parse(text));
}, onProgress, onError);
},
- load方法很简单,主要是传入url地址,然后请求资源,调用对应的回调函数
- 实例化
THREE.XHRLoader,请求数据成功之后,返回文本数据,通过parse函数进行解析(下节再详细解释),返回geometry
THREE.XHRLoader
- 这是THREE内部基于XHR定义的请求加载器
THREE.XHRLoader = function (manager) {
this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
};
THREE.XHRLoader.prototype = {
constructor: THREE.XHRLoader,
...
}
THREE.XHRLoader的构造函数默认使用的也是THREE.DefaultLoadingManager- 主要看下load方法
load: function (url, onLoad, onProgress, onError) {
var scope = this;
// 获取缓存url的数据
var cached = THREE.Cache.get(url);
// 如果有缓存的数据,就直接走缓存缓存
if (cached !== undefined) {
if (onLoad) {
setTimeout(function () {
onLoad(cached);
}, 0);
}
return cached;
}
// 创建 XHR 的 get 请求
var request = new XMLHttpRequest();
request.open('GET', url, true);
// 监听加载状态
request.addEventListener('load', function (event) {
// 获取到响应数据
var response = event.target.response;
// 把url对应的数据缓存起来
THREE.Cache.add(url, response);
// 调用 onLoad回调 返回数据
if (onLoad) onLoad(response);
// 调用加载结束方法
scope.manager.itemEnd(url);
}, false);
// 如果我们定义了 onProgress 回调,监听加载进度
if (onProgress !== undefined) {
request.addEventListener('progress', function (event) {
onProgress(event);
}, false);
}
// 监听加载错误
request.addEventListener('error', function (event) {
// 如果定义了 onError回调 就返回对应的数据
if (onError) onError(event);
scope.manager.itemError(url);
}, false);
// 设置 cross
if (this.crossOrigin !== undefined) request.crossOrigin = this.crossOrigin;
// 设置响应类型
if (this.responseType !== undefined) request.responseType = this.responseType;
// 设置跨域是否携带凭据
if (this.withCredentials !== undefined) request.withCredentials = this.withCredentials;
// 发送请求
request.send(null);
// 调用 itemStart 生命周期
scope.manager.itemStart(url);
return request;
}
- 先是获取缓存数据,如果有缓存数据,就直接返回缓存数据
- 然后通过创建 XHR 的 get 请求,获取数据,把获取的数据缓存起来
- 调用生命周期方法,完成回调通知
THREE.Cache
- three缓存方法实现
THREE.Cache = {
enabled: false, // 是否开启缓存
files: {}, // 缓存数据
add: function (key, file) {
if (this.enabled === false) return;
this.files[key] = file;
},
get: function (key) {
if (this.enabled === false) return;
return this.files[key];
},
remove: function (key) {
delete this.files[key];
},
clear: function () {
this.files = {};
}
};
- enabled:是否开启缓存的开关
- files:缓存的数据其实是一个局部作用域的object对象
- 定义了添加(add),获取(get),移除(remove),清空(clear)方法
函数执行流程
- 核心函数介绍完了,我们来看下整个VTKloader函数的执行流程
总结
这一节我们主要讲了以下内容:
- VTKLoader构造函数
- THREE.DefaultLoadingManager默认加载器
- THREE.LoadingManager加载构造器
- THREE.XHRLoader请求loader
- THREE.Cache缓存实现
- VTKLoader请求资源过程