性能优化:Cesium 懒加载+后台预载、按需渲染、射线检测节流、城市标签 primitive 化、时间轴纹理跟随可视区域
- Cesium 改为动态加载(vite 插件不再注入 6MB 脚本),首屏零 Cesium 请求 - 首帧渲染后后台预载地理视图,loading 展示 8 秒期间并行,就绪前地理按钮置灰 - Three.js 按需渲染:静止停止重绘,动画/交互/状态变化时渲染 - 射线检测合帧节流,binder 只测当前页±1、时间轴只测可视窗口 - 移除 vignette 的 mix-blend-mode 全屏合成 - 7342 个城市从 entity 改为 PointPrimitiveCollection + LabelCollection - 时间轴懒加载窗口跟随可视区域(拖拽/缩放实时跟随)
This commit is contained in:
+71
-12
@@ -21,6 +21,7 @@ export class App {
|
||||
this.timelineSlide = null;
|
||||
this.zoomTarget = null;
|
||||
this.cameraMemory = {};
|
||||
this._needsRender = true; // 按需渲染:首帧必渲染
|
||||
|
||||
this._initRenderer();
|
||||
this._initLights();
|
||||
@@ -36,6 +37,14 @@ export class App {
|
||||
|
||||
this._initEvents();
|
||||
this._applyRotationLimits(CAMERA_POSES.binder);
|
||||
// 默认新打开时:活页本放到最小(相机沿默认视角拉到最远)
|
||||
{
|
||||
const bp = CAMERA_POSES.binder;
|
||||
const dir = bp.pos.clone().sub(bp.target).normalize();
|
||||
this.camera.position.copy(bp.target).addScaledVector(dir, bp.max);
|
||||
this.controls.target.copy(bp.target);
|
||||
this.controls.update();
|
||||
}
|
||||
this._select(0, { syncBinder: false });
|
||||
this.binder.syncTo(0);
|
||||
|
||||
@@ -45,6 +54,13 @@ export class App {
|
||||
requestAnimationFrame(this._animate);
|
||||
}
|
||||
|
||||
// 首屏加载完成后由 main.js 调用:后台预载地理视图,就绪后激活“地理”按钮
|
||||
preloadGlobe() {
|
||||
const btn = document.querySelector('.view-btn[data-view="globe"]');
|
||||
this.globe.viewerReady.then(() => { if (btn) btn.disabled = false; });
|
||||
this.globe.ensureCreated();
|
||||
}
|
||||
|
||||
_initRenderer() {
|
||||
const canvas = document.getElementById('scene');
|
||||
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
||||
@@ -74,6 +90,7 @@ export class App {
|
||||
this.controls.mouseButtons.MIDDLE = -1;
|
||||
this.controls.mouseButtons.RIGHT = -1;
|
||||
this.controls.rotateSpeed = 0.4; // 整体降速(无边缘减速)
|
||||
this.controls.addEventListener('change', () => { this._needsRender = true; });
|
||||
this.controls.update();
|
||||
}
|
||||
|
||||
@@ -84,15 +101,16 @@ export class App {
|
||||
const key = new THREE.DirectionalLight(0xffe6c0, 2.4);
|
||||
key.position.set(3.2, 4.4, 3.6);
|
||||
key.castShadow = true;
|
||||
key.shadow.mapSize.set(1024, 1024);
|
||||
key.shadow.mapSize.set(2048, 2048);
|
||||
key.shadow.camera.near = 0.5;
|
||||
key.shadow.camera.far = 30;
|
||||
key.shadow.camera.left = -5;
|
||||
key.shadow.camera.right = 5;
|
||||
key.shadow.camera.top = 6;
|
||||
key.shadow.camera.bottom = -6;
|
||||
key.shadow.bias = -0.0002;
|
||||
key.shadow.camera.left = -3.5;
|
||||
key.shadow.camera.right = 3.5;
|
||||
key.shadow.camera.top = 4;
|
||||
key.shadow.camera.bottom = -4;
|
||||
key.shadow.bias = -0.0005;
|
||||
this.scene.add(key);
|
||||
this.scene.add(key.target);
|
||||
this.keyLight = key;
|
||||
|
||||
const rim = new THREE.DirectionalLight(0x8fa0d0, 0.7);
|
||||
@@ -116,7 +134,7 @@ export class App {
|
||||
|
||||
this.timeline = new Timeline();
|
||||
this.globe = new CesiumGlobe({ onPick: (i) => this._pickGlobe(i) });
|
||||
this.globe.ensureCreated(); // 打开页面时就渲染缓存好三个视图
|
||||
// Cesium 懒加载:首次进入“地理”视图时才真正创建 viewer
|
||||
|
||||
this.scene.add(this.binder.group);
|
||||
this.scene.add(this.timeline.group);
|
||||
@@ -173,6 +191,7 @@ export class App {
|
||||
this.raycaster = new THREE.Raycaster();
|
||||
this.pointer = new THREE.Vector2();
|
||||
this._drag = null;
|
||||
this._hoverDirty = false;
|
||||
const canvas = this.renderer.domElement;
|
||||
|
||||
const setPointer = (e) => {
|
||||
@@ -207,11 +226,11 @@ export class App {
|
||||
this.timelineOffset = clamp(this._drag.offset + dx * this._worldPerPixel(), -limit, limit);
|
||||
this.timeline.group.position.x = this.timelineOffset;
|
||||
canvas.style.cursor = 'grabbing';
|
||||
this._needsRender = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const hit = this._hitTest();
|
||||
canvas.style.cursor = hit ? 'pointer' : 'grab';
|
||||
this._hoverDirty = true; // 射线检测移到 _animate 中每帧至多执行一次
|
||||
});
|
||||
|
||||
canvas.addEventListener('pointerup', (e) => {
|
||||
@@ -271,8 +290,21 @@ export class App {
|
||||
}
|
||||
|
||||
_hitTest() {
|
||||
const list = this.interactives[this.view];
|
||||
if (!list || !list.length) return null;
|
||||
let list;
|
||||
if (this.view === 'binder') {
|
||||
// 只检测顶层可点击页(左右页 + 翻页缓冲),避免对全部 182 页求交
|
||||
list = [];
|
||||
const cur = this.binder.current;
|
||||
for (let d = -1; d <= 1; d++) {
|
||||
const i = cur + d;
|
||||
if (i >= 0 && i < this.binder.pages.length) list.push(this.binder.pages[i].mesh);
|
||||
}
|
||||
} else if (this.view === 'timeline') {
|
||||
list = this.timeline.pickableMeshes();
|
||||
} else {
|
||||
return null; // 地理视图由 Cesium 自行拾取
|
||||
}
|
||||
if (!list.length) return null;
|
||||
this.raycaster.setFromCamera(this.pointer, this.camera);
|
||||
const hits = this.raycaster.intersectObjects(list, false);
|
||||
return hits.length ? hits[0] : null;
|
||||
@@ -331,10 +363,13 @@ export class App {
|
||||
this.globe.setSelected(this.selected);
|
||||
if (this.view === 'globe') this.globe.flyToEntry(this.selected); // 地球页切换时自动居中
|
||||
if (syncBinder) this.binder.syncTo(this.selected);
|
||||
this._needsRender = true;
|
||||
}
|
||||
|
||||
switchView(view) {
|
||||
if (view === this.view || !CAMERA_POSES[view]) return;
|
||||
// 地理视图尚未就绪(按钮仍置灰)时忽略切换,键盘也无法绕过
|
||||
if (view === 'globe' && !this.globe.viewer) return;
|
||||
// 离开当前视图前,记住它的相机位置
|
||||
if (this.view !== 'globe') {
|
||||
this.cameraMemory[this.view] = {
|
||||
@@ -416,6 +451,7 @@ export class App {
|
||||
this.controls.target.copy(pose.target);
|
||||
}
|
||||
}
|
||||
this._needsRender = true;
|
||||
}
|
||||
|
||||
_applyRotationLimits(pose) {
|
||||
@@ -449,16 +485,27 @@ export class App {
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
if (this.globe) this.globe.resize();
|
||||
this._needsRender = true;
|
||||
}
|
||||
|
||||
_animate() {
|
||||
requestAnimationFrame(this._animate);
|
||||
const dt = Math.min(this.clock.getDelta(), 0.05);
|
||||
|
||||
// 是否有进行中的动画(决定本帧是否需要重绘)
|
||||
const animating =
|
||||
this.binder.flip !== null ||
|
||||
this.timeline.hasActiveFlip() ||
|
||||
this.cameraTween !== null ||
|
||||
this.zoomTarget !== null ||
|
||||
this.timelineSlide !== null;
|
||||
if (animating) this._needsRender = true;
|
||||
|
||||
this.binder.update(dt);
|
||||
this.timeline.update(dt);
|
||||
this.timeline.drainPending();
|
||||
this._updateTimelineTextures();
|
||||
if (this.timeline.consumeDirty()) this._needsRender = true;
|
||||
|
||||
if (this.cameraTween) {
|
||||
const t = this.cameraTween;
|
||||
@@ -492,7 +539,19 @@ export class App {
|
||||
if (k >= 1) this.timelineSlide = null;
|
||||
}
|
||||
|
||||
// 鼠标悬停检测:每帧至多一次(pointermove 高频事件被合帧)
|
||||
if (this._hoverDirty) {
|
||||
this._hoverDirty = false;
|
||||
const hit = this._hitTest();
|
||||
this.renderer.domElement.style.cursor = hit ? 'pointer' : 'grab';
|
||||
}
|
||||
|
||||
this.controls.update();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
|
||||
// 按需渲染:仅在状态/动画/交互变化时重绘,静止时停止渲染
|
||||
if (this._needsRender) {
|
||||
this._needsRender = false;
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user