Write code for a 3D solar system visualization in JavaScript using Three.js. Include 8 planets with accurate orbits, rotation speeds, and tooltips that display data on hover (e.g., distance from sun, gravity, orbital time). Allow zoom and pan with mouse. Make the scene interactive and educational.
HTML
预览
复制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Solar System</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap');
body {
margin: 0;
overflow: hidden;
background-color: #000;
font-family: 'Orbitron', monospace;
cursor: grab;
}
body:active {
cursor: grabbing;
}
#canvas-container {
position: relative;
width: 100vw;
height: 100vh;
}
#info-panel {
position: absolute;
top: 20px;
left: 20px;
background: linear-gradient(135deg, rgba(20, 20, 40, 0.9), rgba(10, 10, 30, 0.9));
color: white;
padding: 20px;
border-radius: 15px;
max-width: 350px;
display: none;
border: 1px solid rgba(100, 100, 255, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#info-panel h3 {
margin-top: 0;
color: #4a9eff;
font-size: 24px;
text-shadow: 0 0 10px rgba(74, 158, 255, 0.5);
margin-bottom: 15px;
}
#info-panel p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
#info-panel .label {
color: #8a8aff;
font-weight: bold;
display: inline-block;
width: 120px;
}
#info-panel .value {
color: #ffffff;
}
.tooltip {
position: absolute;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.9), rgba(20, 20, 40, 0.9));
color: white;
padding: 10px 15px;
border-radius: 8px;
font-size: 14px;
pointer-events: none;
display: none;
border: 1px solid rgba(100, 100, 255, 0.3);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
animation: tooltipFade 0.2s ease-out;
}
@keyframes tooltipFade {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
#controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, rgba(20, 20, 40, 0.9), rgba(10, 10, 30, 0.9));
padding: 15px 30px;
border-radius: 30px;
border: 1px solid rgba(100, 100, 255, 0.3);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
text-align: center;
color: white;
font-size: 14px;
}
#speed-controls {
margin-top: 10px;
}
#speed-slider {
width: 150px;
margin: 0 10px;
vertical-align: middle;
}
#speed-value {
display: inline-block;
width: 30px;
text-align: left;
color: #4a9eff;
}
#title {
position: absolute;
top: 20px;
right: 20px;
color: white;
text-align: right;
}
#title h1 {
margin: 0;
font-size: 36px;
font-weight: 900;
background: linear-gradient(135deg, #4a9eff, #8a8aff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(74, 158, 255, 0.5);
}
#title p {
margin: 5px 0 0 0;
font-size: 14px;
color: #8a8aff;
opacity: 0.8;
}
.planet-label {
position: absolute;
color: white;
font-size: 12px;
pointer-events: none;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.8);
opacity: 0.7;
}
</style>
</head>
<body>
<div id="canvas-container">
<div id="info-panel"></div>
<div class="tooltip"></div>
<div id="controls">
<div>Mouse: Rotate | Scroll: Zoom | Shift+Drag: Pan</div>
<div id="speed-controls">
Speed: <input type="range" id="speed-slider" min="0" max="5" value="1" step="0.1">
<span id="speed-value">1x</span>
</div>
</div>
<div id="title">
<h1>SOLAR SYSTEM</h1>
<p>Interactive 3D Visualization</p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script>
// Speed control
let speedMultiplier = 1;
const speedSlider = document.getElementById('speed-slider');
const speedValue = document.getElementById('speed-value');
speedSlider.addEventListener('input', (e) => {
speedMultiplier = parseFloat(e.target.value);
speedValue.textContent = speedMultiplier + 'x';
});
// Planet data
const planetData = {
mercury: {
name: "Mercury",
radius: 0.38,
distance: 5.8,
orbitPeriod: 0.24,
rotationPeriod: 58.6,
color: 0xC0C0C0,
gravity: "3.7 m/s²",
moons: 0,
temperature: "430°C (day) / -180°C (night)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/mercury.jpg"
},
venus: {
name: "Venus",
radius: 0.95,
distance: 10.8,
orbitPeriod: 0.62,
rotationPeriod: -243,
color: 0xFFA500,
gravity: "8.87 m/s²",
moons: 0,
temperature: "462°C (average)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/venus.jpg"
},
earth: {
name: "Earth",
radius: 1,
distance: 15,
orbitPeriod: 1,
rotationPeriod: 1,
color: 0x0000FF,
gravity: "9.8 m/s²",
moons: 1,
temperature: "15°C (average)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/earth.jpg"
},
mars: {
name: "Mars",
radius: 0.53,
distance: 22.8,
orbitPeriod: 1.88,
rotationPeriod: 1.03,
color: 0xFF4500,
gravity: "3.71 m/s²",
moons: 2,
temperature: "-63°C (average)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/mars.jpg"
},
jupiter: {
name: "Jupiter",
radius: 11.2,
distance: 77.8,
orbitPeriod: 11.86,
rotationPeriod: 0.41,
color: 0xFFA500,
gravity: "24.79 m/s²",
moons: 79,
temperature: "-108°C (cloud top)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/jupiter.jpg"
},
saturn: {
name: "Saturn",
radius: 9.45,
distance: 143,
orbitPeriod: 29.46,
rotationPeriod: 0.45,
color: 0xFFD700,
gravity: "10.44 m/s²",
moons: 82,
temperature: "-139°C (cloud top)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/saturn.jpg"
},
uranus: {
name: "Uranus",
radius: 4.0,
distance: 287.1,
orbitPeriod: 84.01,
rotationPeriod: -0.72,
color: 0x00FFFF,
gravity: "8.69 m/s²",
moons: 27,
temperature: "-197°C (cloud top)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/uranus.jpg"
},
neptune: {
name: "Neptune",
radius: 3.88,
distance: 449.5,
orbitPeriod: 164.8,
rotationPeriod: 0.67,
color: 0x0000FF,
gravity: "11.15 m/s²",
moons: 14,
temperature: "-200°C (cloud top)",
texture: "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/neptune.jpg"
}
};
// Initialize scene
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.00000025);
// Camera setup
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 30, 50);
// Renderer setup
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('canvas-container').appendChild(renderer.domElement);
// Controls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 5;
controls.maxDistance = 500;
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const sunLight = new THREE.PointLight(0xffffff, 2, 1000);
sunLight.castShadow = true;
scene.add(sunLight);
// Add rim lighting for planets
const rimLight = new THREE.DirectionalLight(0x4444ff, 0.3);
rimLight.position.set(100, 100, 100);
scene.add(rimLight);
// Create sun
const sunGeometry = new THREE.SphereGeometry(3, 64, 64);
const sunMaterial = new THREE.MeshBasicMaterial({
color: 0xffff00,
emissive: 0xffff00,
emissiveIntensity: 1
});
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
scene.add(sun);
// Add glow to sun
const sunGlowGeometry = new THREE.SphereGeometry(4, 32, 32);
const sunGlowMaterial = new THREE.MeshBasicMaterial({
color: 0xffaa00,
transparent: true,
opacity: 0.3,
side: THREE.BackSide
});
const sunGlow = new THREE.Mesh(sunGlowGeometry, sunGlowMaterial);
sun.add(sunGlow);
// Add sun corona
const coronaGeometry = new THREE.SphereGeometry(5, 32, 32);
const coronaMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 }
},
vertexShader: `
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float time;
varying vec3 vNormal;
void main() {
float intensity = pow(0.6 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);
gl_FragColor = vec4(1.0, 0.6, 0.0, 1.0) * intensity * (1.0 + 0.2 * sin(time));
}
`,
side: THREE.BackSide,
blending: THREE.AdditiveBlending,
transparent: true
});
const corona = new THREE.Mesh(coronaGeometry, coronaMaterial);
sun.add(corona);
// Create planets
const planets = {};
const planetMeshes = [];
const textureLoader = new THREE.TextureLoader();
// Create starfield
const starsGeometry = new THREE.BufferGeometry();
const starsMaterial = new THREE.PointsMaterial({
color: 0xFFFFFF,
size: 0.7,
sizeAttenuation: true,
blending: THREE.AdditiveBlending
});
const starsVertices = [];
for (let i = 0; i < 10000; i++) {
const x = THREE.MathUtils.randFloatSpread(2000);
const y = THREE.MathUtils.randFloatSpread(2000);
const z = THREE.MathUtils.randFloatSpread(2000);
starsVertices.push(x, y, z);
}
starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3));
const starField = new THREE.Points(starsGeometry, starsMaterial);
scene.add(starField);
// Create planets and orbits
Object.keys(planetData).forEach(planetKey => {
const planet = planetData[planetKey];
// Create orbit
const orbitGeometry = new THREE.RingGeometry(planet.distance, planet.distance + 0.05, 128);
const orbitMaterial = new THREE.MeshBasicMaterial({
color: 0x444444,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.3
});
const orbit = new THREE.Mesh(orbitGeometry, orbitMaterial);
orbit.rotation.x = Math.PI / 2;
scene.add(orbit);
// Create planet
const planetGeometry = new THREE.SphereGeometry(planet.radius, 64, 64);
const planetMaterial = new THREE.MeshPhongMaterial({
map: textureLoader.load(planet.texture)
});
const planetMesh = new THREE.Mesh(planetGeometry, planetMaterial);
planetMesh.userData = {
planetKey: planetKey,
name: planet.name,
distance: planet.distance + " million km",
gravity: planet.gravity,
moons: planet.moons,
temperature: planet.temperature,
orbitPeriod: planet.orbitPeriod + " Earth years",
rotationPeriod: Math.abs(planet.rotationPeriod) + " Earth days" + (planet.rotationPeriod < 0 ? " (retrograde)" : ""),
objectType: 'planet'
};
// Add rings to Saturn
if (planetKey === 'saturn') {
const ringGeometry = new THREE.RingGeometry(planet.radius * 1.2, planet.radius * 2, 64);
const ringMaterial = new THREE.MeshBasicMaterial({
color: 0xFFD700,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6
});
const rings = new THREE.Mesh(ringGeometry, ringMaterial);
rings.rotation.x = Math.PI / 2;
planetMesh.add(rings);
}
// Create planet orbit container
const planetOrbit = new THREE.Object3D();
planetOrbit.add(planetMesh);
scene.add(planetOrbit);
// Position planet
planetMesh.position.x = planet.distance;
planets[planetKey] = {
mesh: planetMesh,
orbit: planetOrbit,
data: planet
};
planetMeshes.push(planetMesh);
});
// Raycaster for interaction
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const tooltip = document.querySelector('.tooltip');
const infoPanel = document.getElementById('info-panel');
// Handle mouse move
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(planetMeshes);
if (intersects.length > 0) {
const planet = intersects[0].object;
const data = planet.userData;
// Show tooltip
tooltip.style.display = 'block';
tooltip.style.left = `${event.clientX + 10}px`;
tooltip.style.top = `${event.clientY + 10}px`;
tooltip.innerHTML = `<strong>${data.name}</strong><br>Click for details`;
document.body.style.cursor = 'pointer';
} else {
tooltip.style.display = 'none';
document.body.style.cursor = 'default';
}
}
// Handle click
function onClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(planetMeshes);
if (intersects.length > 0) {
const planet = intersects[0].object;
const data = planet.userData;
// Show info panel
infoPanel.style.display = 'block';
infoPanel.innerHTML = `
<h3>${data.name}</h3>
<p><span class="label">Distance from Sun:</span> <span class="value">${data.distance}</span></p>
<p><span class="label">Gravity:</span> <span class="value">${data.gravity}</span></p>
<p><span class="label">Orbital Period:</span> <span class="value">${data.orbitPeriod}</span></p>
<p><span class="label">Rotation Period:</span> <span class="value">${data.rotationPeriod}</span></p>
<p><span class="label">Number of Moons:</span> <span class="value">${data.moons}</span></p>
<p><span class="label">Temperature:</span> <span class="value">${data.temperature}</span></p>
`;
} else {
infoPanel.style.display = 'none';
}
}
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', onClick);
// Handle window resize
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', onWindowResize);
// Animation loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
const speed = speedMultiplier;
// Rotate sun
sun.rotation.y = elapsedTime * 0.1;
// Update corona shader
corona.material.uniforms.time.value = elapsedTime;
// Update planets
Object.keys(planets).forEach(planetKey => {
const planet = planets[planetKey];
// Orbit around the sun
planet.orbit.rotation.y = elapsedTime * (1 / planet.data.orbitPeriod) * 0.5 * speed;
// Rotate the planet
planet.mesh.rotation.y = elapsedTime * (1 / planet.data.rotationPeriod) * 5 * speed;
});
controls.update();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>