加载动画 · Loading Progress · ▶ 在线运行案例

加载动画

你将学到什么

  • LoadingManager 管理多资源总进度
  • GLTFLoader.load 第三个参数 xhr 下载进度
  • 两层进度:下载 %解析/导入 %

效果说明

加载 LittlestTokyo.glb 大场景时,屏幕中央浮层显示 「下载 xx%」「导入 xx%」,完成后显示「加载完成」。

核心概念

LoadingManager 回调

const manager = new THREE.LoadingManager();
manager.onStart = (url, loaded, total) => { /* 开始 */ };
manager.onProgress = (url, loaded, total) => { /* 总进度 */ };
manager.onLoad = () => { /* 全部完成 */ };
manager.onError = (url) => { /* 失败 */ };

const loader = new GLTFLoader(manager);

传入 Manager 后,Loader 内部加载的 纹理、bin 等子资源 都会计入 onProgress

xhr 下载进度

loader.load(url, onLoad, (xhr) => {
    const pct = (xhr.loaded / xhr.total * 100).toFixed(2);
    // 仅反映网络下载,不含 GPU 解析
});
阶段 回调 含义
下载 load 的第 3 参数 xhr 字节流进度
导入 manager.onProgress 含纹理解析等

本案例 两者同时显示,用户体验更完整。

代码要点

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'

const loadingDiv = document.createElement('div')
loadingDiv.innerText = '加载中...'
Object.assign(loadingDiv.style, {
    pointerEvents: 'none',
    position: 'fixed',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%,-50%)',
    color: 'white',
    fontSize: '20px',
    backgroundColor: 'rgba(0,0,0,0.5)',
    padding: '10px 20px',
    borderRadius: '5px'
})

document.body.appendChild(loadingDiv)

const box = document.getElementById('box')

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000000)

camera.position.set(0, 400, 400)

const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })

renderer.setSize(box.clientWidth, box.clientHeight)

box.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)

controls.enableDamping = true

window.onresize = () => {

    renderer.setSize(box.clientWidth, box.clientHeight)

    camera.aspect = box.clientWidth / box.clientHeight

    camera.updateProjectionMatrix()

}

scene.add(new THREE.AmbientLight(0xffffff, 3))


const manager = new THREE.LoadingManager();

manager.onStart = function (url, itemsLoaded, itemsTotal) {
    loadingDiv.innerText = '开始加载'
};

manager.onLoad = function () {
    loadingDiv.innerHTML = '加载完成'
};

manager.onProgress = function (url, itemsLoaded, itemsTotal) {
    loadingDiv.innerText = '导入' + (itemsLoaded / itemsTotal * 100).toFixed(2) + '%' 
}

manager.onError = function (url) {

}

const loader = new GLTFLoader(manager)

loader.setDRACOLoader(new DRACOLoader().setDecoderPath(FILE_HOST + 'js/three/draco/'))

loader.load(

    FILE_HOST + '/files/model/LittlestTokyo.glb?time=' + new Date().getTime(),

    gltf => {

        scene.add(gltf.scene)

    },

    xhr => {

        loadingDiv.innerText = '下载' + (xhr.loaded / xhr.total * 100).toFixed(2) + '%'

    }

)

animate()

function animate() {

    requestAnimationFrame(animate)

    controls.update()

    renderer.render(scene, camera)

}

完整源码:GitHub

小结