From 225ec71ac38c7fab4f1877b2a23387a6851e5498 Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Mon, 6 Apr 2026 02:38:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=86=B2=E7=AA=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/js/app.js | 113 +++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 6d21b40..fdcc891 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -207,19 +207,20 @@ dom.btnChangePassword.addEventListener('click', saveChangePassword); } - // Globe expansion (FLIP animation) + // Globe expansion (FLIP animation via Web Animations API) let savedGlobeRect = null; + let globeAnimating = false; function expandGlobe() { - if (dom.globeCard.classList.contains('expanded')) return; + if (dom.globeCard.classList.contains('expanded') || globeAnimating) return; + globeAnimating = true; - // Save original rect for FLIP + // FLIP: capture original position savedGlobeRect = dom.globeCard.getBoundingClientRect(); + // Apply expanded state dom.globeCard.classList.add('expanded'); dom.btnExpandGlobe.classList.add('active'); - - // Update button icon/title dom.btnExpandGlobe.title = '缩小显示'; dom.btnExpandGlobe.innerHTML = ` @@ -227,59 +228,53 @@ `; + // Resize ECharts immediately (prevents flash) if (myMap2D) myMap2D.resize(); + // FLIP: capture expanded position const endRect = dom.globeCard.getBoundingClientRect(); const scaleX = savedGlobeRect.width / endRect.width; const scaleY = savedGlobeRect.height / endRect.height; - - // Using top-left for math (transformOrigin: 0 0) const dx = savedGlobeRect.left - endRect.left; const dy = savedGlobeRect.top - endRect.top; - dom.globeCard.style.willChange = 'transform, box-shadow'; - dom.globeCard.style.transformOrigin = '0 0'; - dom.globeCard.style.transition = 'none'; - 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'; + // Animate using Web Animations API (bypasses all CSS conflicts) + const anim = dom.globeCard.animate([ + { + transform: `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`, + boxShadow: '0 0 0 0 transparent, 0 0 0 0 transparent', + offset: 0 + }, + { + transform: 'translate(0, 0) scale(1)', + boxShadow: '0 0 80px rgba(0,0,0,0.8), 0 0 0 100vh rgba(0,0,0,0.65)', + offset: 1 + } + ], { + duration: 400, + easing: 'cubic-bezier(0.16, 1, 0.3, 1)', + fill: 'none' + }); - dom.globeCard.offsetHeight; // Force reflow - - dom.globeCard.style.transition = 'transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s ease'; - dom.globeCard.style.transform = 'translate(0px, 0px) scale(1)'; - dom.globeCard.style.boxShadow = ''; - - const onTransitionEnd = (e) => { - if (e.propertyName !== 'transform') return; - dom.globeCard.removeEventListener('transitionend', onTransitionEnd); - dom.globeCard.style.transition = ''; - dom.globeCard.style.willChange = ''; - dom.globeCard.style.transformOrigin = ''; - dom.globeCard.style.transform = ''; + anim.onfinish = () => { + globeAnimating = false; }; - dom.globeCard.addEventListener('transitionend', onTransitionEnd); } function collapseGlobe() { - if (!dom.globeCard.classList.contains('expanded')) return; - if (dom.globeCard.classList.contains('globe-collapsing')) return; + if (!dom.globeCard.classList.contains('expanded') || globeAnimating) return; + globeAnimating = true; 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 = ` `; - + globeAnimating = false; if (myMap2D) requestAnimationFrame(() => myMap2D.resize()); }; @@ -290,43 +285,35 @@ dom.globeCard.classList.add('globe-collapsing'); + // FLIP: compute target transform to original position const expandedRect = dom.globeCard.getBoundingClientRect(); const scaleX = savedGlobeRect.width / expandedRect.width; const scaleY = savedGlobeRect.height / expandedRect.height; - - // Match origin used in expand const dx = savedGlobeRect.left - expandedRect.left; const dy = savedGlobeRect.top - expandedRect.top; - // Force setup the expanded state with origin - dom.globeCard.style.transformOrigin = '0 0'; - dom.globeCard.style.transform = 'translate(0px, 0px) scale(1)'; - dom.globeCard.style.boxShadow = ''; // Reset to what CSS says - dom.globeCard.offsetHeight; // Critical sync reflow + // Animate from current expanded state to original rect + const anim = dom.globeCard.animate([ + { + transform: 'translate(0, 0) scale(1)', + boxShadow: '0 0 80px rgba(0,0,0,0.8), 0 0 0 100vh rgba(0,0,0,0.65)', + offset: 0 + }, + { + transform: `translate(${dx}px, ${dy}px) scale(${scaleX}, ${scaleY})`, + boxShadow: '0 0 0 0 transparent, 0 0 0 0 transparent', + offset: 1 + } + ], { + duration: 350, + easing: 'cubic-bezier(0.4, 0, 0.2, 1)', + fill: 'forwards' // Hold final frame until we remove class + }); - 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'; - - // Apply target values immediately after reflow - 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); + anim.onfinish = () => { + anim.cancel(); // Release the fill-forwards hold resetState(); }; - dom.globeCard.addEventListener('transitionend', onTransitionEnd); - - // Robustness fallback - setTimeout(() => { - if (!transitionFinished) { - dom.globeCard.removeEventListener('transitionend', onTransitionEnd); - resetState(); - } - }, 500); } if (dom.btnExpandGlobe) {