diff --git a/public/css/style.css b/public/css/style.css
index b4b0323..7f3fc27 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -2005,6 +2005,10 @@ input:checked+.slider:before {
opacity: 1 !important;
}
+.globe-card.globe-collapsing {
+ pointer-events: none;
+}
+
.globe-card.expanded .globe-body {
height: calc(90vh - 120px) !important; /* Explicit calc height for ECharts reliability */
width: 100% !important;
diff --git a/public/js/app.js b/public/js/app.js
index bc218b5..8db8e2a 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -213,18 +213,22 @@
function expandGlobe() {
if (dom.globeCard.classList.contains('expanded')) return;
- // FLIP Step 1 — First: save original rect
+ // Save original rect for FLIP
savedGlobeRect = dom.globeCard.getBoundingClientRect();
- // FLIP Step 2 — Last: apply expanded state
dom.globeCard.classList.add('expanded');
dom.btnExpandGlobe.classList.add('active');
+
+ // Update button icon/title
+ dom.btnExpandGlobe.title = '缩小显示';
+ dom.btnExpandGlobe.innerHTML = `
+
+ `;
- // Resize ECharts IMMEDIATELY so the map renders at full size
- // This prevents the "flash" — content is correctly sized throughout
if (myMap2D) myMap2D.resize();
- // FLIP Step 3 — Invert: calculate and apply inverse transform
const endRect = dom.globeCard.getBoundingClientRect();
const scaleX = savedGlobeRect.width / endRect.width;
const scaleY = savedGlobeRect.height / endRect.height;
@@ -236,36 +240,51 @@
dom.globeCard.style.transform = `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`;
dom.globeCard.style.boxShadow = '0 0 0 0 transparent, 0 0 0 0 transparent';
- // Force reflow to commit the inverse state
- dom.globeCard.offsetHeight;
+ dom.globeCard.offsetHeight; // Force reflow
- // FLIP Step 4 — Play: animate to final state
dom.globeCard.style.transition = 'transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s ease';
dom.globeCard.style.transform = '';
dom.globeCard.style.boxShadow = '';
- dom.globeCard.addEventListener('transitionend', function onEnd(e) {
+ const onTransitionEnd = (e) => {
if (e.propertyName !== 'transform') return;
- dom.globeCard.removeEventListener('transitionend', onEnd);
+ dom.globeCard.removeEventListener('transitionend', onTransitionEnd);
dom.globeCard.style.transition = '';
dom.globeCard.style.willChange = '';
- });
+ };
+ dom.globeCard.addEventListener('transitionend', onTransitionEnd);
}
function collapseGlobe() {
if (!dom.globeCard.classList.contains('expanded')) return;
if (dom.globeCard.classList.contains('globe-collapsing')) return;
- if (!savedGlobeRect) {
- dom.globeCard.classList.remove('expanded');
+ const resetState = () => {
+ dom.globeCard.classList.remove('expanded', 'globe-collapsing');
dom.btnExpandGlobe.classList.remove('active');
+ dom.globeCard.style.transition = '';
+ dom.globeCard.style.transform = '';
+ dom.globeCard.style.boxShadow = '';
+ dom.globeCard.style.willChange = '';
+
+ // Restore button icon/title
+ dom.btnExpandGlobe.title = '放大显示';
+ dom.btnExpandGlobe.innerHTML = `
+
+ `;
+
if (myMap2D) requestAnimationFrame(() => myMap2D.resize());
+ };
+
+ if (!savedGlobeRect) {
+ resetState();
return;
}
dom.globeCard.classList.add('globe-collapsing');
- // Calculate transform from expanded back to original position
const expandedRect = dom.globeCard.getBoundingClientRect();
const scaleX = savedGlobeRect.width / expandedRect.width;
const scaleY = savedGlobeRect.height / expandedRect.height;
@@ -274,20 +293,29 @@
dom.globeCard.style.willChange = 'transform, box-shadow';
dom.globeCard.style.transition = 'transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.35s ease';
- dom.globeCard.style.transform = `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`;
- dom.globeCard.style.boxShadow = '0 0 0 0 transparent, 0 0 0 0 transparent';
-
- dom.globeCard.addEventListener('transitionend', function onEnd(e) {
- if (e.propertyName !== 'transform') return;
- dom.globeCard.removeEventListener('transitionend', onEnd);
- dom.globeCard.classList.remove('expanded', 'globe-collapsing');
- dom.btnExpandGlobe.classList.remove('active');
- dom.globeCard.style.transition = '';
- dom.globeCard.style.transform = '';
- dom.globeCard.style.boxShadow = '';
- dom.globeCard.style.willChange = '';
- if (myMap2D) requestAnimationFrame(() => myMap2D.resize());
+
+ // Force change to ensure transition fires
+ requestAnimationFrame(() => {
+ dom.globeCard.style.transform = `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`;
+ dom.globeCard.style.boxShadow = '0 0 0 0 transparent, 0 0 0 0 transparent';
});
+
+ let transitionFinished = false;
+ const onTransitionEnd = (e) => {
+ if (e.propertyName !== 'transform') return;
+ transitionFinished = true;
+ dom.globeCard.removeEventListener('transitionend', onTransitionEnd);
+ resetState();
+ };
+ dom.globeCard.addEventListener('transitionend', onTransitionEnd);
+
+ // Robustness fallback (400ms > 350ms transition)
+ setTimeout(() => {
+ if (!transitionFinished) {
+ dom.globeCard.removeEventListener('transitionend', onTransitionEnd);
+ resetState();
+ }
+ }, 500);
}
if (dom.btnExpandGlobe) {