性能优化: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:
@@ -124,13 +124,20 @@ export class CesiumGlobe {
|
||||
this._visible = false;
|
||||
this._pendingData = 2; // 国家边界 + 城市数据
|
||||
this.ready = new Promise((resolve) => { this._resolveReady = resolve; });
|
||||
this.viewerReady = new Promise((resolve) => { this._resolveViewerReady = resolve; });
|
||||
}
|
||||
|
||||
async ensureCreated() {
|
||||
if (this._created) return;
|
||||
this._created = true;
|
||||
|
||||
await loadCesium();
|
||||
try {
|
||||
await loadCesium();
|
||||
} catch (err) {
|
||||
this._created = false; // 失败可重试
|
||||
console.warn('[CesiumGlobe] Cesium 脚本加载失败:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 调整实体渲染顺序:先渲染标签(城市/国家名),再渲染 Billboard(标记便签),
|
||||
// 让词条标记始终显示在城市/国家名的上层。
|
||||
@@ -255,6 +262,12 @@ export class CesiumGlobe {
|
||||
this.setVisible(this._visible);
|
||||
this.resize();
|
||||
this.flyToEntry(this.selected);
|
||||
|
||||
// 地球已就绪(影像/地形/城市数据继续后台加载),通知 App 隐藏加载遮罩
|
||||
if (this._resolveViewerReady) {
|
||||
this._resolveViewerReady();
|
||||
this._resolveViewerReady = null;
|
||||
}
|
||||
}
|
||||
|
||||
_buildMarkers() {
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import { clamp, easeOutCubic } from '../utils/easing.js';
|
||||
|
||||
const RING_R = 0.085;
|
||||
const RING_TUBE = 0.0115;
|
||||
const STACK_GAP = 0.004;
|
||||
const STACK_GAP = 0.002;
|
||||
const PAGE_GAP = 0.02; // 页内缘离书脊的小空隙
|
||||
|
||||
function makeLeatherTexture({ emblem = false } = {}) {
|
||||
@@ -533,7 +533,7 @@ export class Binder {
|
||||
flipForward() {
|
||||
if (this.flip) this._finalizeFlip(); // 前一页没翻完也能继续翻
|
||||
if (!this.canFlipForward()) return;
|
||||
this.flip = { index: this.current, from: 0, to: -Math.PI, t: 0, duration: 0.6 };
|
||||
this.flip = { index: this.current, from: 0, to: -Math.PI, t: 0, duration: 0.38 };
|
||||
// 优先更新底部导航与信息卡(翻页开始即切换目标页)
|
||||
if (this.onChange) this.onChange((this.current + 1) % this.pages.length);
|
||||
}
|
||||
@@ -541,7 +541,7 @@ export class Binder {
|
||||
flipBackward() {
|
||||
if (this.flip) this._finalizeFlip();
|
||||
if (!this.canFlipBackward()) return;
|
||||
this.flip = { index: this.current - 1, from: -Math.PI, to: 0, t: 0, duration: 0.6 };
|
||||
this.flip = { index: this.current - 1, from: -Math.PI, to: 0, t: 0, duration: 0.38 };
|
||||
// 优先更新底部导航与信息卡
|
||||
if (this.onChange) this.onChange(this.current - 1);
|
||||
}
|
||||
|
||||
+31
-2
@@ -27,6 +27,7 @@ export class Timeline {
|
||||
this._visRange = null;
|
||||
this._ready = false;
|
||||
this._pending = [];
|
||||
this._dirty = false;
|
||||
|
||||
this._buildLine();
|
||||
this._buildCards();
|
||||
@@ -141,17 +142,21 @@ export class Timeline {
|
||||
c.material.setPageMaps(this.placeholder.front, this.placeholder.back);
|
||||
c.isPlaceholder = true;
|
||||
this._textured.delete(i);
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
// 进入窗口:分帧异步生成,避免一次性生成 25 页阻塞主线程
|
||||
this._pending = [...win].filter((i) => !this._textured.has(i) && this.cards[i].isPlaceholder);
|
||||
const next = [...win].filter((i) => !this._textured.has(i) && this.cards[i].isPlaceholder);
|
||||
if (next.length) this._dirty = true;
|
||||
this._pending = next;
|
||||
this.drainPending();
|
||||
}
|
||||
|
||||
// 每帧由 App._animate 驱动,逐批生成窗口内纹理(避免阻塞)
|
||||
drainPending() {
|
||||
if (!this._pending || !this._pending.length) return;
|
||||
if (!this._pending || !this._pending.length) return false;
|
||||
const budget = 2; // 每帧最多生成 2 页
|
||||
let did = false;
|
||||
for (let k = 0; k < budget && this._pending.length; k++) {
|
||||
const i = this._pending.shift();
|
||||
const c = this.cards[i];
|
||||
@@ -160,7 +165,21 @@ export class Timeline {
|
||||
c.material.setPageMaps(front, back);
|
||||
c.isPlaceholder = false;
|
||||
this._textured.add(i);
|
||||
this._dirty = true;
|
||||
did = true;
|
||||
}
|
||||
return did;
|
||||
}
|
||||
|
||||
hasActiveFlip() {
|
||||
return this.cards.some((c) => c.flipAnim);
|
||||
}
|
||||
|
||||
// App 每帧消费“纹理窗口变化”标志,用于按需渲染
|
||||
consumeDirty() {
|
||||
const d = this._dirty;
|
||||
this._dirty = false;
|
||||
return d;
|
||||
}
|
||||
|
||||
// 返回某张卡片在时间轴组内的横向坐标(用于点击后居中)
|
||||
@@ -168,6 +187,16 @@ export class Timeline {
|
||||
return cardX(index, ENTRIES.length);
|
||||
}
|
||||
|
||||
// 射线检测用:只返回可视窗口内的卡片 mesh(减少求交对象)
|
||||
pickableMeshes() {
|
||||
if (!this._visRange) return this.cards.map((c) => c.mesh);
|
||||
const list = [];
|
||||
for (let i = this._visRange.min; i <= this._visRange.max; i++) {
|
||||
if (this.cards[i]) list.push(this.cards[i].mesh);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// 点击翻转:每次点击切换正/反面;选中另一张时,其它翻到背面的卡片先翻回正面
|
||||
flip(index) {
|
||||
this.cards.forEach((o) => {
|
||||
|
||||
Reference in New Issue
Block a user