Compare commits

1 Commits
dev ... main

Author SHA1 Message Date
linkong
49a9c33836 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
2026-03-20 17:13:02 +08:00
8 changed files with 103 additions and 58 deletions

View File

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

View File

@@ -38,8 +38,7 @@
<div id="right-toolbar-group">
<div id="zoom-toolbar">
<button id="reset-view" class="zoom-btn">🎯</button>
<input type="range" id="zoom-slider" min="0.5" max="5" step="0.01" value="1">
<button id="reset-view" class="zoom-btn">📍</button>
<button id="zoom-in" class="zoom-btn">+</button>
<span id="zoom-value" class="zoom-percent">100%</span>
<button id="zoom-out" class="zoom-btn"></button>
@@ -48,8 +47,9 @@
<div id="control-toolbar">
<div class="toolbar-items">
<button id="rotate-toggle" class="toolbar-btn" title="自动旋转">🔄<span class="tooltip">自动旋转</span></button>
<button id="toggle-cables" class="toolbar-btn active" title="显示/隐藏线缆">🌐<span class="tooltip">隐藏线缆</span></button>
<button id="toggle-terrain" class="toolbar-btn" title="显示/隐藏地形">⛰️<span class="tooltip">显示/隐藏地形</span></button>
<button id="toggle-satellites" class="toolbar-btn" title="显示/隐藏卫星">🛰️<span class="tooltip">显示/隐藏卫星</span></button>
<button id="toggle-satellites" class="toolbar-btn active" title="显示/隐藏卫星">🛰️<span class="tooltip">隐藏卫星</span></button>
<button id="toggle-trails" class="toolbar-btn" title="显示/隐藏轨迹"><span class="tooltip">显示/隐藏轨迹</span></button>
<button id="reload-data" class="toolbar-btn" title="重新加载数据">🔃<span class="tooltip">重新加载数据</span></button>
</div>

View File

@@ -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;
}

View File

@@ -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
};

View File

@@ -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');

View File

@@ -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');

View File

@@ -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;
}
}

View File

@@ -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';
}