Improve Cesium preview loading state

This commit is contained in:
2026-07-31 18:11:21 +08:00
parent 0def373c09
commit 7f4ebe8fb7
3 changed files with 168 additions and 3 deletions

View File

@@ -739,6 +739,16 @@ function cesiumPreviewHtml(glbName, metadataName, routeName, vehicleModelName, a
</div> </div>
<div id="diagnostics">Loading diagnostics...</div> <div id="diagnostics">Loading diagnostics...</div>
<div id="status">Loading ${escapeHtml(glbName)}...</div> <div id="status">Loading ${escapeHtml(glbName)}...</div>
<div id="loadingOverlay">
<div class="loading-card">
<div class="loading-spinner" aria-hidden="true"></div>
<div class="loading-copy">
<strong>Loading scene</strong>
<span>${escapeHtml(areaId)}</span>
</div>
<div class="loading-bar" aria-hidden="true"><span></span></div>
</div>
</div>
<script>window.OSM_ASSET_PREVIEW_CONFIG = ${escapeScriptJson(JSON.stringify(previewConfig))};</script> <script>window.OSM_ASSET_PREVIEW_CONFIG = ${escapeScriptJson(JSON.stringify(previewConfig))};</script>
<script src="cesium-preview.js"></script> <script src="cesium-preview.js"></script>
</body> </body>

View File

@@ -9,6 +9,15 @@ body,
background: #d9e0e2; background: #d9e0e2;
} }
#cesiumContainer {
opacity: 0;
transition: opacity 180ms ease-out;
}
body.scene-ready #cesiumContainer {
opacity: 1;
}
#controls, #controls,
#diagnostics, #diagnostics,
#status { #status {
@@ -22,6 +31,110 @@ body,
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
} }
#loadingOverlay {
position: absolute;
inset: 0;
z-index: 3;
display: grid;
place-items: center;
background: #d9e0e2;
color: #1b2528;
font-size: 13px;
letter-spacing: 0;
transition: opacity 220ms ease-out, visibility 220ms ease-out;
}
.loading-card {
display: grid;
grid-template-columns: 32px minmax(180px, 260px);
gap: 12px;
align-items: center;
padding: 14px 16px;
border: 1px solid rgba(70, 86, 90, 0.16);
border-radius: 6px;
background: rgba(244, 248, 248, 0.88);
box-shadow: 0 12px 34px rgba(54, 68, 72, 0.20);
}
.loading-spinner {
width: 28px;
height: 28px;
border: 3px solid rgba(42, 67, 72, 0.18);
border-top-color: #147d8a;
border-radius: 50%;
animation: loading-spin 840ms linear infinite;
}
.loading-copy {
display: grid;
gap: 3px;
}
.loading-copy strong {
font-size: 13px;
font-weight: 650;
}
#loadingOverlay span {
color: #526064;
}
.loading-bar {
grid-column: 1 / -1;
position: relative;
height: 3px;
overflow: hidden;
border-radius: 999px;
background: rgba(42, 67, 72, 0.12);
}
.loading-bar span {
position: absolute;
inset: 0 auto 0 0;
width: 42%;
border-radius: inherit;
background: #147d8a;
animation: loading-bar 1.2s ease-in-out infinite;
}
@keyframes loading-spin {
to {
transform: rotate(360deg);
}
}
@keyframes loading-bar {
0% {
transform: translateX(-110%);
}
55% {
transform: translateX(70%);
}
100% {
transform: translateX(250%);
}
}
body.scene-ready #loadingOverlay {
visibility: hidden;
opacity: 0;
}
body.scene-error #loadingOverlay {
color: #5b1414;
}
body.scene-error .loading-spinner {
border-color: rgba(120, 30, 30, 0.18);
border-top-color: #9f2f2f;
animation-play-state: paused;
}
body.scene-error .loading-bar span {
background: #9f2f2f;
animation-play-state: paused;
}
#controls { #controls {
top: 12px; top: 12px;
left: 12px; left: 12px;

View File

@@ -4,6 +4,7 @@
const config = window.OSM_ASSET_PREVIEW_CONFIG || {}; const config = window.OSM_ASSET_PREVIEW_CONFIG || {};
const statusEl = document.getElementById("status"); const statusEl = document.getElementById("status");
const diagnosticsEl = document.getElementById("diagnostics"); const diagnosticsEl = document.getElementById("diagnostics");
const loadingOverlay = document.getElementById("loadingOverlay");
const toggleCruise = document.getElementById("toggleCruise"); const toggleCruise = document.getElementById("toggleCruise");
const toggleFollow = document.getElementById("toggleFollow"); const toggleFollow = document.getElementById("toggleFollow");
const toggleScene = document.getElementById("toggleScene"); const toggleScene = document.getElementById("toggleScene");
@@ -22,14 +23,17 @@
// from a control the user just touched. Keep the summary so the transient // from a control the user just touched. Keep the summary so the transient
// note can be replaced instead of destroying it. // note can be replaced instead of destroying it.
let baseStatus = ""; let baseStatus = "";
const PREVIEW_BACKGROUND = "#d9e0e2";
Cesium.Ion.defaultAccessToken = ""; Cesium.Ion.defaultAccessToken = "";
async function main() { async function main() {
setLoadingMessage("Loading scene", config.areaId || "");
const metadata = await fetchJson(config.metadataName); const metadata = await fetchJson(config.metadataName);
const routeData = await fetchOptionalJson(config.routeName); const routeData = await fetchOptionalJson(config.routeName);
const placement = scenePlacement(metadata); const placement = scenePlacement(metadata);
const viewer = createViewer(); const viewer = createViewer();
setLoadingMessage("Loading model", config.glbName || "");
const assets = await loadSceneAssets(viewer, metadata, placement); const assets = await loadSceneAssets(viewer, metadata, placement);
const cruise = addVehicleCruises(viewer, routeData, config.vehicleModelName); const cruise = addVehicleCruises(viewer, routeData, config.vehicleModelName);
const cameras = createCameraPresets(viewer, metadata, placement, cruise); const cameras = createCameraPresets(viewer, metadata, placement, cruise);
@@ -40,6 +44,9 @@
cameras.overview(); cameras.overview();
baseStatus = summaryText(metadata, assets, cruise); baseStatus = summaryText(metadata, assets, cruise);
setStatus(baseStatus); setStatus(baseStatus);
setLoadingMessage("Preparing view", "finalizing materials");
await waitForStableFrames(viewer);
document.body.classList.add("scene-ready");
// Handle for the browser console and for headless checks: everything else // Handle for the browser console and for headless checks: everything else
// in here is closed over by the IIFE and unreachable from outside. // in here is closed over by the IIFE and unreachable from outside.
window.osmPreview = { viewer, metadata, placement, assets, cruise, cameras }; window.osmPreview = { viewer, metadata, placement, assets, cruise, cameras };
@@ -78,16 +85,49 @@
selectionIndicator: false, selectionIndicator: false,
baseLayer: false baseLayer: false
}); });
viewer.scene.globe.depthTestAgainstTerrain = true; viewer.scene.globe.show = false;
viewer.scene.backgroundColor = Cesium.Color.fromCssColorString("#d9e0e2"); viewer.scene.globe.depthTestAgainstTerrain = false;
viewer.scene.backgroundColor = Cesium.Color.fromCssColorString(PREVIEW_BACKGROUND);
viewer.scene.skyAtmosphere.show = false; viewer.scene.skyAtmosphere.show = false;
viewer.scene.skyBox.show = false; viewer.scene.skyBox.show = false;
viewer.scene.sun.show = false; viewer.scene.sun.show = false;
viewer.scene.moon.show = false; viewer.scene.moon.show = false;
viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString("#d4dcde"); viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString(PREVIEW_BACKGROUND);
return viewer; return viewer;
} }
function setLoadingMessage(title, detail) {
if (!loadingOverlay) return;
const titleEl = loadingOverlay.querySelector("strong");
const detailEl = loadingOverlay.querySelector("span");
if (titleEl) titleEl.textContent = title;
if (detailEl) detailEl.textContent = detail || "";
}
function waitForStableFrames(viewer) {
return new Promise((resolve) => {
const started = performance.now();
let frames = 0;
let done = false;
let remove = function () {};
function finish() {
if (done) return;
done = true;
remove();
resolve();
}
remove = viewer.scene.postRender.addEventListener(() => {
frames += 1;
const elapsed = performance.now() - started;
if ((frames >= 14 && elapsed >= 650) || elapsed >= 2800) {
finish();
}
});
window.setTimeout(finish, 3200);
viewer.scene.requestRender();
});
}
function scenePlacement(metadata) { function scenePlacement(metadata) {
const anchor = metadata.anchor || {}; const anchor = metadata.anchor || {};
const longitude = Number(anchor.longitude || 0); const longitude = Number(anchor.longitude || 0);
@@ -626,5 +666,7 @@
main().catch((error) => { main().catch((error) => {
console.error(error); console.error(error);
setStatus("Failed to load Cesium preview: " + error.message); setStatus("Failed to load Cesium preview: " + error.message);
document.body.classList.add("scene-error");
setLoadingMessage("Failed to load scene", error.message);
}); });
}()); }());