性能优化: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:
2026-09-19 03:21:27 +08:00
parent 0c7990a9a8
commit f701862e5a
8 changed files with 175 additions and 32 deletions
+71 -12
View File
@@ -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);
}
}
}