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) {