From 49a9c338361ee7ff4b281bf0ef49bc363c229fa9 Mon Sep 17 00:00:00 2001 From: linkong Date: Fri, 20 Mar 2026 17:13:02 +0800 Subject: [PATCH] feat(earth): toolbar and zoom improvements - Add box-sizing/padding normalization to toolbar buttons - Remove zoom slider, implement click/hold zoom behavior (+/- buttons) - Add 10% step on click, 1% continuous on hold - Fix satellite init: show satellite points immediately, delay trail visibility - Fix breathing effect: faster pulse, wider opacity range - Add toggle-cables functionality with visibility state - Initialize satellites and cables as visible by default --- frontend/public/earth/css/base.css | 7 +- frontend/public/earth/index.html | 6 +- frontend/public/earth/js/cables.js | 15 ++++ frontend/public/earth/js/constants.js | 4 +- frontend/public/earth/js/controls.js | 111 ++++++++++++++----------- frontend/public/earth/js/main.js | 4 +- frontend/public/earth/js/satellites.js | 11 ++- frontend/public/earth/js/ui.js | 3 +- 8 files changed, 103 insertions(+), 58 deletions(-) diff --git a/frontend/public/earth/css/base.css b/frontend/public/earth/css/base.css index 62d0e4e5..7448c835 100644 --- a/frontend/public/earth/css/base.css +++ b/frontend/public/earth/css/base.css @@ -43,7 +43,7 @@ body { display: flex; flex-direction: column; align-items: center; - gap: 4px; + gap: 6px; background: rgba(10, 10, 30, 0.9); padding: 8px 4px; border-radius: 24px; @@ -93,7 +93,9 @@ body { justify-content: center; transition: all 0.2s ease; padding: 0; + margin: 0; flex: 0 0 auto; + box-sizing: border-box; } #zoom-toolbar .zoom-btn:hover { @@ -306,6 +308,9 @@ input[type="range"]::-webkit-slider-thumb { align-items: center; justify-content: center; transition: all 0.2s ease; + box-sizing: border-box; + padding: 0; + margin: 0; } .toolbar-btn:hover { diff --git a/frontend/public/earth/index.html b/frontend/public/earth/index.html index 21db28a5..c5fd858c 100644 --- a/frontend/public/earth/index.html +++ b/frontend/public/earth/index.html @@ -38,8 +38,7 @@
- - + 100% @@ -48,8 +47,9 @@
+ - +
diff --git a/frontend/public/earth/js/cables.js b/frontend/public/earth/js/cables.js index be42a82c..8bdfa33f 100644 --- a/frontend/public/earth/js/cables.js +++ b/frontend/public/earth/js/cables.js @@ -11,6 +11,7 @@ export let cableLines = []; export let landingPoints = []; export let lockedCable = null; let cableIdMap = new Map(); +let cablesVisible = true; function getCableColor(properties) { if (properties.color) { @@ -406,3 +407,17 @@ export function resetLandingPointVisualState() { lp.scale.setScalar(1.0); }); } + +export function toggleCables(show) { + cablesVisible = show; + cableLines.forEach(cable => { + cable.visible = cablesVisible; + }); + landingPoints.forEach(lp => { + lp.visible = cablesVisible; + }); +} + +export function getShowCables() { + return cablesVisible; +} diff --git a/frontend/public/earth/js/constants.js b/frontend/public/earth/js/constants.js index 79a796b1..d6e846a6 100644 --- a/frontend/public/earth/js/constants.js +++ b/frontend/public/earth/js/constants.js @@ -39,11 +39,11 @@ export const CABLE_COLORS = { }; export const CABLE_CONFIG = { - lockedOpacityMin: 0.6, + lockedOpacityMin: 0.2, lockedOpacityMax: 1.0, otherOpacity: 0.5, otherBrightness: 0.6, - pulseSpeed: 0.003, + pulseSpeed: 0.008, pulseCoefficient: 0.4 }; diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js index 45d9cdc0..75d1457d 100644 --- a/frontend/public/earth/js/controls.js +++ b/frontend/public/earth/js/controls.js @@ -5,6 +5,7 @@ import { updateZoomDisplay, showStatusMessage } from './ui.js'; import { toggleTerrain } from './earth.js'; import { reloadData } from './main.js'; import { toggleSatellites, toggleTrails, getShowSatellites, getSatelliteCount } from './satellites.js'; +import { toggleCables, getShowCables } from './cables.js'; export let autoRotate = true; export let zoomLevel = 1.0; @@ -23,33 +24,18 @@ export function setupControls(camera, renderer, scene, earth) { function setupZoomControls(camera) { let zoomInterval = null; - let lastDirection = 0; - let isSnapped = false; + let holdTimeout = null; + let startTime = 0; + const HOLD_THRESHOLD = 150; + const LONG_PRESS_TICK = 50; + const CLICK_STEP = 10; const MIN_PERCENT = CONFIG.minZoom * 100; const MAX_PERCENT = CONFIG.maxZoom * 100; - function adjustZoom(direction) { - const currentPercent = Math.round(zoomLevel * 100); - let newPercent; - - if (direction > 0) { - if (!isSnapped || currentPercent % 10 !== 0) { - newPercent = Math.ceil(currentPercent / 10) * 10; - if (newPercent <= currentPercent) newPercent = currentPercent + 10; - isSnapped = true; - } else { - newPercent = currentPercent + 10; - } - } else { - if (!isSnapped || currentPercent % 10 !== 0) { - newPercent = Math.floor(currentPercent / 10) * 10; - if (newPercent >= currentPercent) newPercent = currentPercent - 10; - isSnapped = true; - } else { - newPercent = currentPercent - 10; - } - } + function doZoomStep(direction) { + let currentPercent = Math.round(zoomLevel * 100); + let newPercent = direction > 0 ? currentPercent + CLICK_STEP : currentPercent - CLICK_STEP; if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; @@ -58,13 +44,22 @@ function setupZoomControls(camera) { applyZoom(camera); } - function startZoom(direction) { - isSnapped = false; - lastDirection = direction; - adjustZoom(direction); + function doContinuousZoom(direction) { + let currentPercent = Math.round(zoomLevel * 100); + let newPercent = direction > 0 ? currentPercent + 1 : currentPercent - 1; + + if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; + if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; + + zoomLevel = newPercent / 100; + applyZoom(camera); + } + + function startContinuousZoom(direction) { + doContinuousZoom(direction); zoomInterval = setInterval(() => { - adjustZoom(direction); - }, 150); + doContinuousZoom(direction); + }, LONG_PRESS_TICK); } function stopZoom() { @@ -72,29 +67,49 @@ function setupZoomControls(camera) { clearInterval(zoomInterval); zoomInterval = null; } + if (holdTimeout) { + clearTimeout(holdTimeout); + holdTimeout = null; + } } - document.getElementById('zoom-in').addEventListener('mousedown', () => startZoom(1)); - document.getElementById('zoom-in').addEventListener('mouseup', stopZoom); - document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom); - document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(1); }); - document.getElementById('zoom-in').addEventListener('touchend', stopZoom); + function handleMouseDown(direction) { + startTime = Date.now(); + stopZoom(); + holdTimeout = setTimeout(() => { + startContinuousZoom(direction); + }, HOLD_THRESHOLD); + } - document.getElementById('zoom-out').addEventListener('mousedown', () => startZoom(-1)); - document.getElementById('zoom-out').addEventListener('mouseup', stopZoom); + function handleMouseUp(direction) { + const heldTime = Date.now() - startTime; + stopZoom(); + if (heldTime < HOLD_THRESHOLD) { + doZoomStep(direction); + } + } + + document.getElementById('zoom-in').addEventListener('mousedown', () => handleMouseDown(1)); + document.getElementById('zoom-in').addEventListener('mouseup', () => handleMouseUp(1)); + document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom); + document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(1); }); + document.getElementById('zoom-in').addEventListener('touchend', () => handleMouseUp(1)); + + document.getElementById('zoom-out').addEventListener('mousedown', () => handleMouseDown(-1)); + document.getElementById('zoom-out').addEventListener('mouseup', () => handleMouseUp(-1)); document.getElementById('zoom-out').addEventListener('mouseleave', stopZoom); - document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(-1); }); - document.getElementById('zoom-out').addEventListener('touchend', stopZoom); + document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(-1); }); + document.getElementById('zoom-out').addEventListener('touchend', () => handleMouseUp(-1)); document.getElementById('zoom-value').addEventListener('click', function() { - const startZoom = zoomLevel; + const startZoomVal = zoomLevel; const targetZoom = 1.0; - const startDistance = CONFIG.defaultCameraZ / startZoom; + const startDistance = CONFIG.defaultCameraZ / startZoomVal; const targetDistance = CONFIG.defaultCameraZ / targetZoom; animateValue(0, 1, 600, (progress) => { const ease = 1 - Math.pow(1 - progress, 3); - zoomLevel = startZoom + (targetZoom - startZoom) * ease; + zoomLevel = startZoomVal + (targetZoom - startZoomVal) * ease; camera.position.z = CONFIG.defaultCameraZ / zoomLevel; const distance = startDistance + (targetDistance - startDistance) * ease; updateZoomDisplay(zoomLevel, distance.toFixed(0)); @@ -103,12 +118,6 @@ function setupZoomControls(camera) { showStatusMessage('缩放已重置到100%', 'info'); }); }); - - const slider = document.getElementById('zoom-slider'); - slider?.addEventListener('input', (e) => { - zoomLevel = parseFloat(e.target.value); - applyZoom(camera); - }); } function setupWheelZoom(camera, renderer) { @@ -235,6 +244,14 @@ function setupTerrainControls() { showStatusMessage(showTrails ? '轨迹已显示' : '轨迹已隐藏', 'info'); }); + document.getElementById('toggle-cables').addEventListener('click', function() { + const showCables = !getShowCables(); + toggleCables(showCables); + this.classList.toggle('active', showCables); + this.querySelector('.tooltip').textContent = showCables ? '隐藏线缆' : '显示线缆'; + showStatusMessage(showCables ? '线缆已显示' : '线缆已隐藏', 'info'); + }); + document.getElementById('reload-data').addEventListener('click', async () => { await reloadData(); showStatusMessage('数据已重新加载', 'success'); diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index fcda5cb1..ed01a1aa 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -92,7 +92,7 @@ function applyCableVisualState() { switch (state) { case CABLE_STATE.LOCKED: - c.material.opacity = 0.3 + pulse * 0.7; + c.material.opacity = CABLE_CONFIG.lockedOpacityMin + pulse * (CABLE_CONFIG.lockedOpacityMax - CABLE_CONFIG.lockedOpacityMin); c.material.color.setRGB(1, 1, 1); break; case CABLE_STATE.HOVERED: @@ -201,6 +201,8 @@ async function loadData(showWhiteSphere = false) { console.log(`卫星数据加载完成: ${satCount} 颗`); updateSatellitePositions(); console.log('卫星位置已更新'); + toggleSatellites(true); + console.log('卫星已显示'); } catch (error) { console.error('加载数据失败:', error); showStatusMessage('加载数据失败: ' + error.message, 'error'); diff --git a/frontend/public/earth/js/satellites.js b/frontend/public/earth/js/satellites.js index 60b6c879..c8693d1b 100644 --- a/frontend/public/earth/js/satellites.js +++ b/frontend/public/earth/js/satellites.js @@ -7,8 +7,9 @@ import { CONFIG, SATELLITE_CONFIG } from './constants.js'; let satellitePoints = null; let satelliteTrails = null; let satelliteData = []; -let showSatellites = false; +let showSatellites = true; let showTrails = true; +let trailsReady = false; let animationTime = 0; let selectedSatellite = null; let satellitePositions = []; @@ -320,6 +321,10 @@ export function updateSatellitePositions(deltaTime = 0) { satelliteTrails.geometry.attributes.position.needsUpdate = true; satelliteTrails.geometry.attributes.color.needsUpdate = true; + + if (!trailsReady && count > 0 && satellitePositions[0]?.trail.length >= TRAIL_LENGTH) { + trailsReady = true; + } } export function toggleSatellites(visible) { @@ -328,14 +333,14 @@ export function toggleSatellites(visible) { satellitePoints.visible = visible; } if (satelliteTrails) { - satelliteTrails.visible = visible && showTrails; + satelliteTrails.visible = visible && showTrails && trailsReady; } } export function toggleTrails(visible) { showTrails = visible; if (satelliteTrails) { - satelliteTrails.visible = visible && showSatellites; + satelliteTrails.visible = visible && showSatellites && trailsReady; } } diff --git a/frontend/public/earth/js/ui.js b/frontend/public/earth/js/ui.js index 67b82bb6..da36d359 100644 --- a/frontend/public/earth/js/ui.js +++ b/frontend/public/earth/js/ui.js @@ -25,7 +25,8 @@ export function updateZoomDisplay(zoomLevel, distance) { const percent = Math.round(zoomLevel * 100); document.getElementById('zoom-value').textContent = percent + '%'; document.getElementById('zoom-level').textContent = '缩放: ' + percent + '%'; - document.getElementById('zoom-slider').value = zoomLevel; + const slider = document.getElementById('zoom-slider'); + if (slider) slider.value = zoomLevel; document.getElementById('camera-distance').textContent = distance + ' km'; }