Let’s create an Interactive 3D Galaxy Animation using HTML, CSS, and JavaScript. This project will feature a stunning galaxy filled with glowing stars that move and respond to user interaction, creating a mesmerizing 3D space effect.
We’ll use:
- HTML to structure the canvas and elements.
- CSS to style the background and lighting effects.
- JavaScript to create and animate the 3D galaxy, using mouse movement or scroll for interactivity.
This project is perfect for developers who love visual creativity and want to explore 3D effects in web design. Let’s dive into this cosmic coding adventure and bring the galaxy to life right in your browser! 🌌✨
HTML :
This code builds an interactive 3D galaxy animation using HTML, CSS, and JavaScript with Three.js. It creates a main glowing star in the center, surrounded by rotating planets and shining particle trails. The camera moves smoothly around the scene, and everything is lit with colorful lights and bloom effects to make it look realistic and futuristic.
There are also buttons that let users control the animation – you can reset the camera view, change how fast time moves, switch between beautiful color themes like Inferno, Veridian, and Celestial, or activate a “resonance” mode that makes the planets glow and connect with energy arcs. Overall, the project turns code into a stunning 3D galaxy that feels alive and interactive.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Interactive 3D Galaxy Animation | @coding.stella</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="container"></div>
<div class="stats" id="stats">
<div>Systems: Active</div>
<div>Resonance: Stable</div>
<div>Phase: Nominal</div>
</div>
<div class="controls">
<button class="control-button" id="resetView">
<span></span><span></span><span></span><span></span><span>Reset View</span>
</button>
<button class="control-button activate-button" id="activateTrigger">
<span></span><span></span><span></span><span></span><span>Activate Resonance</span>
</button>
<button class="control-button" id="timeAccel">
<span></span><span></span><span></span><span></span><span>Time: 1x</span>
</button>
<button class="control-button" id="toggleTheme">
<span></span><span></span><span></span><span></span><span>Theme: Inferno</span>
</button>
</div>
</body>
<script id="starVertexShader" type="x-shader/x-vertex">
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUv;
void main(){
vPosition=position;
vNormal=normal;
vUv=uv;
gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);
}
</script>
<script id="starFragmentShader" type="x-shader/x-fragment">
uniform float time;
uniform float intensity;
uniform vec3 color1;
uniform vec3 color2;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUv;
float noise(vec3 p){return fract(sin(dot(p,vec3(12.9898,78.233,54.53)))*43758.5453);}
void main(){
vec3 pos=vPosition+time*0.1;
float n1=noise(pos*3.0);
float n2=noise(pos*6.0+vec3(100.0));
float n3=noise(pos*12.0+vec3(200.0));
float pattern=n1*0.5+n2*0.3+n3*0.2;
pattern=pow(pattern,2.0);
vec3 finalColor=mix(color1,color2,pattern);
finalColor*=(1.0+intensity*pattern*2.0);
float fresnel=pow(1.0-dot(vNormal,vec3(0.0,0.0,1.0)),2.0);
finalColor+=fresnel*intensity*0.5;
gl_FragColor=vec4(finalColor,1.0);
}
</script>
<script id="planetVertexShader" type="x-shader/x-vertex">
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUv;
void main(){
vPosition=position;
vNormal=normalize(normalMatrix*normal);
vUv=uv;
gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);
}
</script>
<script id="planetFragmentShader" type="x-shader/x-fragment">
uniform float time;
uniform vec3 baseColor;
uniform vec3 accentColor;
uniform float energy;
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUv;
float noise(vec2 p){return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);}
void main(){
vec2 uv=vUv+time*0.02;
float n1=noise(uv*8.0);
float n2=noise(uv*16.0);
float pattern=n1*0.7+n2*0.3;
vec3 color=mix(baseColor,accentColor,pattern);
float fresnel=pow(1.0-abs(dot(vNormal,vec3(0.0,0.0,1.0))),1.5);
color+=fresnel*accentColor*0.5;
color*=(1.0+energy*0.8);
gl_FragColor=vec4(color,1.0);
}
</script>
<script id="arcVertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
varying vec3 vPosition;
void main(){
vUv=uv;
vPosition=position;
gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);
}
</script>
<script id="arcFragmentShader" type="x-shader/x-fragment">
uniform float time;
uniform vec3 color;
uniform float opacity;
uniform float energy;
varying vec2 vUv;
varying vec3 vPosition;
void main(){
float flow=abs(sin(vUv.x*15.0-time*12.0));
float pulse=sin(time*8.0)*0.5+0.5;
float pattern=pow(flow,1.5)*(1.0+pulse*energy);
float fade=sin(vUv.x*3.14159);
vec3 finalColor=color*(pattern*2.0+0.3);
float alpha=fade*opacity*(pattern+0.2)*(1.0+energy);
gl_FragColor=vec4(finalColor,alpha);
}
</script>
<script type="importmap">
{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.161.0/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.161.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
let scene, camera, renderer, controls, clock, composer;
let star,
orrery,
starLight,
ambientLight,
blueLight,
purpleLight,
metalMaterial,
ringMaterial;
let planets = [];
let activeEffects = [];
let particleSystems = [];
let timeAcceleration = 1.0;
let isResonanceActive = false;
let currentThemeIndex = 0;
const container = document.getElementById("container");
const activateButton = document.getElementById("activateTrigger");
const resetButton = document.getElementById("resetView");
const timeButton = document.getElementById("timeAccel");
const themeButton = document.getElementById("toggleTheme");
const themes = [
{
name: "Inferno",
starColors: { color1: 0xffffff, color2: 0xffcc00 },
planetData: [
{
baseColor: [0.8, 0.2, 0.1],
accentColor: [1, 0.6, 0.2],
trailColor: 0xff4400,
},
{
baseColor: [0.6, 0.1, 0.1],
accentColor: [1, 0.4, 0.1],
trailColor: 0xff8800,
},
{
baseColor: [0.9, 0.3, 0],
accentColor: [1, 0.8, 0.3],
trailColor: 0xffaa33,
},
],
ambientLightColor: 0x401008,
starLightColor: 0xffcc88,
directionalLights: { color1: 0xff6600, color2: 0xdd3300 },
metalMaterialColor: 0x332222,
ringColor: 0xff8866,
arcColor: 0xffccaa,
},
{
name: "Veridian",
starColors: { color1: 0xccffee, color2: 0x66ffcc },
planetData: [
{
baseColor: [0.2, 0.8, 0.5],
accentColor: [0.8, 1, 0.9],
trailColor: 0x00ffaa,
},
{
baseColor: [0.1, 0.6, 0.7],
accentColor: [0.5, 0.9, 1],
trailColor: 0x00ccff,
},
{
baseColor: [0.5, 0.8, 0.2],
accentColor: [0.9, 1, 0.6],
trailColor: 0xaaff00,
},
],
ambientLightColor: 0x0a3024,
starLightColor: 0xccffdd,
directionalLights: { color1: 0x33cc88, color2: 0x4488cc },
metalMaterialColor: 0x779988,
ringColor: 0x88ffcc,
arcColor: 0xeeffee,
},
{
name: "Celestial",
starColors: { color1: 0xffe4b5, color2: 0xff8844 },
planetData: [
{
baseColor: [1, 0.4, 0.4],
accentColor: [1, 0.8, 0.2],
trailColor: 0xff6644,
},
{
baseColor: [0.3, 0.8, 0.3],
accentColor: [0.6, 1, 0.8],
trailColor: 0x44ff88,
},
{
baseColor: [0.3, 0.4, 1],
accentColor: [0.8, 0.6, 1],
trailColor: 0x4488ff,
},
],
ambientLightColor: 0x1a2440,
starLightColor: 0xffe4b5,
directionalLights: { color1: 0x4488ff, color2: 0x8844ff },
metalMaterialColor: 0x4a6080,
ringColor: 0x88ccff,
arcColor: 0xffeebb,
},
];
function init() {
scene = new THREE.Scene();
clock = new THREE.Clock();
camera = new THREE.PerspectiveCamera(
55,
window.innerWidth / window.innerHeight,
0.1,
3000,
);
camera.position.set(25, 20, 25);
renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance",
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.03;
controls.minDistance = 8;
controls.maxDistance = 150;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.15;
controls.enablePan = false;
setupLighting();
createOrrery();
createEnvironment();
setupPostProcessing();
applyTheme(currentThemeIndex);
window.addEventListener("resize", onWindowResize);
activateButton.addEventListener("click", activateResonance);
resetButton.addEventListener("click", resetView);
timeButton.addEventListener("click", toggleTimeAcceleration);
themeButton.addEventListener("click", toggleTheme);
}
function setupLighting() {
ambientLight = new THREE.AmbientLight(0x1a2440, 0.8);
scene.add(ambientLight);
starLight = new THREE.PointLight(0xffe4b5, 3, 120, 1.8);
starLight.castShadow = true;
starLight.shadow.mapSize.width = 2048;
starLight.shadow.mapSize.height = 2048;
scene.add(starLight);
blueLight = new THREE.DirectionalLight(0x4488ff, 0.5);
blueLight.position.set(-50, 30, -30);
scene.add(blueLight);
purpleLight = new THREE.DirectionalLight(0x8844ff, 0.3);
purpleLight.position.set(30, -20, 50);
scene.add(purpleLight);
}
function setupPostProcessing() {
composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.6,
0.5,
0.15,
);
composer.addPass(bloomPass);
composer.addPass(new OutputPass());
}
function createOrrery() {
orrery = new THREE.Group();
scene.add(orrery);
metalMaterial = new THREE.MeshStandardMaterial({
color: 0x4a6080,
metalness: 0.95,
roughness: 0.2,
emissive: 0x1a2540,
emissiveIntensity: 0.4,
envMapIntensity: 1.5,
});
ringMaterial = new THREE.MeshBasicMaterial({
color: 0x88ccff,
wireframe: true,
transparent: true,
opacity: 0.4,
});
const starGeo = new THREE.IcosahedronGeometry(2.2, 2);
const starMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
intensity: { value: 1 },
color1: {
value: new THREE.Color(themes[currentThemeIndex].starColors.color1),
},
color2: {
value: new THREE.Color(themes[currentThemeIndex].starColors.color2),
},
},
vertexShader: document.getElementById("starVertexShader").textContent,
fragmentShader:
document.getElementById("starFragmentShader").textContent,
});
star = new THREE.Mesh(starGeo, starMaterial);
star.castShadow = false;
star.receiveShadow = false;
orrery.add(star);
for (let i = 0; i < 7; i++) {
const gearGeo = new THREE.TorusGeometry(2.8 + i * 0.5, 0.18, 12, 64);
const gear = new THREE.Mesh(gearGeo, metalMaterial);
gear.rotation.x = Math.PI / 2;
gear.position.y = -2 - i * 0.25;
gear.userData.rotationSpeed =
(i % 2 === 0 ? 1 : -1) * (0.08 + i * 0.04);
gear.castShadow = true;
gear.receiveShadow = true;
orrery.add(gear);
}
const planetGeometries = [
new THREE.OctahedronGeometry(0.6, 1),
new THREE.DodecahedronGeometry(0.9, 1),
new THREE.IcosahedronGeometry(0.7, 1),
];
const planetBaseData = [
{ size: 0.6, distance: 8, speed: 0.6 },
{ size: 0.9, distance: 14, speed: 0.35 },
{ size: 0.7, distance: 22, speed: 0.25 },
];
planetBaseData.forEach((data, i) => {
const planetGroup = new THREE.Group();
planetGroup.userData.orbitSpeed = data.speed;
planetGroup.rotation.y = Math.random() * Math.PI * 2;
orrery.add(planetGroup);
const ringGeo = new THREE.TorusGeometry(data.distance, 0.08, 20, 128);
const ring = new THREE.Mesh(ringGeo, ringMaterial);
ring.rotation.x = Math.PI / 2;
planetGroup.add(ring);
const planetMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
baseColor: {
value: new THREE.Vector3(
...themes[currentThemeIndex].planetData[i].baseColor,
),
},
accentColor: {
value: new THREE.Vector3(
...themes[currentThemeIndex].planetData[i].accentColor,
),
},
energy: { value: 0 },
},
vertexShader:
document.getElementById("planetVertexShader").textContent,
fragmentShader: document.getElementById("planetFragmentShader")
.textContent,
});
const planet = new THREE.Mesh(planetGeometries[i], planetMaterial);
planet.position.x = data.distance;
planet.userData.selfRotation = 0.6;
planet.castShadow = true;
planet.receiveShadow = true;
planetGroup.add(planet);
createParticleTrail(
planet,
themes[currentThemeIndex].planetData[i].trailColor,
data.distance,
);
planets.push({
group: planetGroup,
body: planet,
material: planetMaterial,
});
});
}
function createParticleTrail(planet, color, radius) {
const count = 50;
const positions = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
const sizes = new Float32Array(count);
const c = new THREE.Color(color);
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2;
positions[i * 3] = Math.cos(angle) * radius;
positions[i * 3 + 1] = 0;
positions[i * 3 + 2] = Math.sin(angle) * radius;
colors[i * 3] = c.r;
colors[i * 3 + 1] = c.g;
colors[i * 3 + 2] = c.b;
sizes[i] = Math.random() * 0.5 + 0.1;
}
const geom = new THREE.BufferGeometry();
geom.setAttribute("position", new THREE.BufferAttribute(positions, 3));
geom.setAttribute("color", new THREE.BufferAttribute(colors, 3));
geom.setAttribute("size", new THREE.BufferAttribute(sizes, 1));
const mat = new THREE.PointsMaterial({
size: 0.3,
vertexColors: true,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending,
sizeAttenuation: true,
});
const points = new THREE.Points(geom, mat);
planet.parent.add(points);
particleSystems.push({
system: points,
planet: planet,
positions: positions,
radius: radius,
currentIndex: 0,
});
}
function createEnvironment() {
const layers = [
{
count: 3000,
distance: [600, 1000],
size: [0.8, 1.5],
color: 0x6688bb,
},
{ count: 2000, distance: [1000, 1500], size: [1, 2], color: 0x88aadd },
{
count: 1000,
distance: [1500, 2000],
size: [1.5, 3],
color: 0xaaccff,
},
];
layers.forEach((layer) => {
const positions = new Float32Array(layer.count * 3);
const colors = new Float32Array(layer.count * 3);
const sizes = new Float32Array(layer.count);
const c = new THREE.Color(layer.color);
for (let i = 0; i < layer.count; i++) {
const u = Math.random(),
v = Math.random();
const theta = 2 * Math.PI * u;
const phi = Math.acos(2 * v - 1);
const r =
layer.distance[0] +
Math.random() * (layer.distance[1] - layer.distance[0]);
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i * 3 + 2] = r * Math.cos(phi);
colors[i * 3] = c.r;
colors[i * 3 + 1] = c.g;
colors[i * 3 + 2] = c.b;
sizes[i] =
layer.size[0] + Math.random() * (layer.size[1] - layer.size[0]);
}
const geom = new THREE.BufferGeometry();
geom.setAttribute("position", new THREE.BufferAttribute(positions, 3));
geom.setAttribute("color", new THREE.BufferAttribute(colors, 3));
geom.setAttribute("size", new THREE.BufferAttribute(sizes, 1));
const mat = new THREE.PointsMaterial({
size: 2,
vertexColors: true,
sizeAttenuation: true,
transparent: true,
opacity: 0.8,
});
const stars = new THREE.Points(geom, mat);
scene.add(stars);
});
}
function activateResonance() {
if (isResonanceActive) return;
isResonanceActive = true;
planets.forEach(
(p) => (p.body.userData.baseOrbitSpeed = p.group.userData.orbitSpeed),
);
themes; // preserve reference
const activationEffect = {
type: "activation",
startTime: clock.getElapsedTime(),
duration: 8,
update: (elapsed) => {
const prog = elapsed / activationEffect.duration;
const intens = Math.sin(prog * Math.PI) * 3;
starLight.intensity = 3 + intens * 3;
star.material.uniforms.intensity.value = 1 + intens * 2;
metalMaterial.emissiveIntensity = 0.4 + intens * 2;
planets.forEach((p) => {
p.group.userData.orbitSpeed =
p.body.userData.baseOrbitSpeed * (1 + intens * 1.5);
p.material.uniforms.energy.value = intens;
});
},
end: () => {
starLight.intensity = 3;
star.material.uniforms.intensity.value = 1;
metalMaterial.emissiveIntensity = 0.4;
planets.forEach((p) => {
p.group.userData.orbitSpeed = p.body.userData.baseOrbitSpeed;
p.material.uniforms.energy.value = 0;
});
isResonanceActive = false;
},
};
activeEffects.push(activationEffect);
for (let i = 0; i < planets.length; i++) {
createEnhancedArc(
planets[i].body,
i === 0 ? star : planets[i - 1].body,
6,
);
}
}
function createEnhancedArc(obj1, obj2, duration) {
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(themes[currentThemeIndex].arcColor) },
opacity: { value: 0 },
energy: { value: 0 },
},
vertexShader: document.getElementById("arcVertexShader").textContent,
fragmentShader:
document.getElementById("arcFragmentShader").textContent,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
const arcEffect = {
type: "arc",
mesh: null,
material: material,
startTime: clock.getElapsedTime(),
duration: duration,
obj1: obj1,
obj2: obj2,
update: (elapsed) => {
const prog = elapsed / arcEffect.duration;
if (prog > 1) return;
const p1 = new THREE.Vector3(),
p2 = new THREE.Vector3();
obj1.getWorldPosition(p1);
obj2.getWorldPosition(p2);
const mid = p1.clone().lerp(p2, 0.5);
const ctrl = mid
.clone()
.add(new THREE.Vector3(0, p1.distanceTo(p2) * 0.5, 0));
const curve = new THREE.QuadraticBezierCurve3(p1, ctrl, p2);
const tube = new THREE.TubeGeometry(curve, 48, 0.15, 12, false);
if (!arcEffect.mesh) {
arcEffect.mesh = new THREE.Mesh(tube, material);
scene.add(arcEffect.mesh);
} else {
arcEffect.mesh.geometry.dispose();
arcEffect.mesh.geometry = tube;
}
const intens = Math.sin(prog * Math.PI);
material.uniforms.opacity.value = intens * 0.9;
material.uniforms.energy.value = intens * 2;
material.uniforms.time.value = clock.getElapsedTime();
arcEffect.mesh.visible = true;
},
end: () => {
if (arcEffect.mesh) {
scene.remove(arcEffect.mesh);
arcEffect.mesh.geometry.dispose();
arcEffect.material.dispose();
}
},
};
activeEffects.push(arcEffect);
}
function resetView() {
controls.reset();
camera.position.set(25, 20, 25);
controls.update();
}
function toggleTimeAcceleration() {
timeAcceleration =
timeAcceleration === 1 ? 3 : timeAcceleration === 3 ? 0.5 : 1;
document
.getElementById("timeAccel")
.querySelector("span:last-of-type").textContent =
`Time: ${timeAcceleration}x`;
}
function toggleTheme() {
currentThemeIndex = (currentThemeIndex + 1) % themes.length;
applyTheme(currentThemeIndex);
}
function applyTheme(i) {
const theme = themes[i];
themeButton.querySelector("span:last-of-type").textContent =
`Theme: ${theme.name}`;
star.material.uniforms.color1.value.set(theme.starColors.color1);
star.material.uniforms.color2.value.set(theme.starColors.color2);
planets.forEach((p, idx) => {
const pd = theme.planetData[idx];
p.material.uniforms.baseColor.value.set(...pd.baseColor);
p.material.uniforms.accentColor.value.set(...pd.accentColor);
});
particleSystems.forEach((ps, idx) => {
const c = new THREE.Color(theme.planetData[idx].trailColor);
const col = ps.system.geometry.attributes.color;
for (let j = 0; j < col.count; j++) col.setXYZ(j, c.r, c.g, c.b);
col.needsUpdate = true;
});
ambientLight.color.set(theme.ambientLightColor);
starLight.color.set(theme.starLightColor);
blueLight.color.set(theme.directionalLights.color1);
purpleLight.color.set(theme.directionalLights.color2);
metalMaterial.color.set(theme.metalMaterialColor);
ringMaterial.color.set(theme.ringColor);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta() * timeAcceleration;
const t = clock.getElapsedTime();
star.rotation.y += delta * 0.3;
star.rotation.x += delta * 0.15;
star.material.uniforms.time.value = t;
orrery.children.forEach((c) => {
if (c.userData.rotationSpeed)
c.rotation.z += delta * c.userData.rotationSpeed;
});
planets.forEach((p) => {
p.group.rotation.y += delta * p.group.userData.orbitSpeed;
p.body.rotation.y += delta * p.body.userData.selfRotation;
p.body.rotation.z += delta * p.body.userData.selfRotation * 0.3;
p.material.uniforms.time.value = t;
});
particleSystems.forEach(
(ps) => (ps.system.geometry.attributes.position.needsUpdate = true),
);
for (let i = activeEffects.length - 1; i >= 0; i--) {
const e = activeEffects[i];
const elapsed = t - e.startTime;
if (elapsed > e.duration) {
e.end();
activeEffects.splice(i, 1);
} else {
e.update(elapsed, delta);
}
}
controls.update();
composer.render(delta);
}
init();
animate();
</script>
</html>
CSS :
This CSS gives the 3D galaxy page a futuristic, glowing look with dark space-style backgrounds, neon buttons, and glass-like panels. The control buttons have animated borders, hover effects, and a glowing “Activate Resonance” button for emphasis. The stats box and controls use blur and transparency for a clean sci-fi feel, and everything adjusts nicely on mobile screens.
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@300;500;700;900&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
body {
margin: 0;
overflow: hidden;
background: radial-gradient(circle at center, #0a0f1a 0%, #020408 100%);
height: 100vh;
width: 100vw;
font-family: "Inter", "Orbitron", sans-serif;
}
#container {
width: 100%;
height: 100%;
display: block;
position: absolute;
top: 0;
left: 0;
}
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
align-items: center;
padding: 8px;
background: rgba(255, 255, 255, 0.01);
backdrop-filter: blur(40px) saturate(180%);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.02),
inset 0 1px 0 rgba(255, 255, 255, 0.1),
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
}
.control-button {
position: relative;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 12px;
padding: 10px 18px;
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
user-select: none;
letter-spacing: 0.5px;
min-width: 100px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex-shrink: 0;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24),
inset 0 1px 0 rgba(255, 255, 255, 0.15),
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
}
.control-button:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 1);
transform: translateY(-2px);
box-shadow:
0 12px 36px rgba(0, 0, 0, 0.2),
0 1px 3px rgba(0, 0, 0, 0.24),
inset 0 1px 0 rgba(255, 255, 255, 0.15),
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
}
.control-button:active {
background: rgba(255, 255, 255, 0.15);
transform: translateY(0);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24),
inset 0 2px 4px rgba(0, 0, 0, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
.control-button > span:last-of-type {
position: relative;
z-index: 2;
}
.control-button > span:not(:last-of-type) {
position: absolute;
display: block;
box-shadow:
0 0 15px rgba(128, 200, 255, 0.9),
0 0 30px rgba(128, 200, 255, 0.7);
}
.control-button > span:nth-child(1) {
top: 0;
left: -100%;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(128, 200, 255, 1));
}
.control-button:hover > span:nth-child(1) {
animation: animateBorder1 2s linear infinite;
}
.control-button > span:nth-child(2) {
top: -100%;
right: 0;
width: 2px;
height: 100%;
background: linear-gradient(180deg, transparent, rgba(128, 200, 255, 1));
}
.control-button:hover > span:nth-child(2) {
animation: animateBorder2 2s linear infinite;
animation-delay: 0.5s;
}
.control-button > span:nth-child(3) {
bottom: 0;
right: -100%;
width: 100%;
height: 2px;
background: linear-gradient(270deg, transparent, rgba(128, 200, 255, 1));
}
.control-button:hover > span:nth-child(3) {
animation: animateBorder3 2s linear infinite;
animation-delay: 1s;
}
.control-button > span:nth-child(4) {
bottom: -100%;
left: 0;
width: 2px;
height: 100%;
background: linear-gradient(0deg, transparent, rgba(128, 200, 255, 1));
}
.control-button:hover > span:nth-child(4) {
animation: animateBorder4 2s linear infinite;
animation-delay: 1.5s;
}
@keyframes animateBorder1 {
0% {
left: -100%;
}
50%,100% {
left: 100%;
}
}
@keyframes animateBorder2 {
0% {
top: -100%;
}
50%,100% {
top: 100%;
}
}
@keyframes animateBorder3 {
0% {
right: -100%;
}
50%,100% {
right: 100%;
}
}
@keyframes animateBorder4 {
0% {
bottom: -100%;
}
50%,
100% {
bottom: 100%;
}
}
.activate-button {
background: linear-gradient(
135deg,
rgba(255, 100, 100, 0.15) 0%,
rgba(255, 50, 50, 0.1) 100%
);
border: 1px solid rgba(255, 100, 100, 0.3);
color: rgba(255, 150, 150, 0.95);
}
.activate-button:hover {
background: linear-gradient(
135deg,
rgba(255, 120, 120, 0.2) 0%,
rgba(255, 80, 80, 0.15) 100%
);
border-color: rgba(255, 150, 150, 0.4);
color: rgba(255, 200, 200, 1);
}
.activate-button > span:not(:last-of-type) {
box-shadow:
0 0 15px rgba(255, 120, 120, 0.9),
0 0 30px rgba(255, 120, 120, 0.7);
}
.activate-button > span:nth-child(1) {
background: linear-gradient(90deg, transparent, rgba(255, 120, 120, 1));
}
.activate-button > span:nth-child(2) {
background: linear-gradient(180deg, transparent, rgba(255, 120, 120, 1));
}
.activate-button > span:nth-child(3) {
background: linear-gradient(270deg, transparent, rgba(255, 120, 120, 1));
}
.activate-button > span:nth-child(4) {
background: linear-gradient(0deg, transparent, rgba(255, 120, 120, 1));
}
.stats {
position: absolute;
top: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 10px;
font-weight: 400;
pointer-events: none;
z-index: 5;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
line-height: 1.6;
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(20px) saturate(150%);
padding: 10px 14px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.stats div {
margin-bottom: 4px;
padding: 2px 0;
}
.stats div:last-child {
margin-bottom: 0;
}
.controls::before {
content: "";
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
background: linear-gradient(
45deg,
rgba(255, 255, 255, 0.05) 0%,
transparent 25%,
transparent 75%,
rgba(255, 255, 255, 0.05) 100%
);
border-radius: 21px;
z-index: -1;
opacity: 0.5;
}
@media (max-width: 640px) {
.controls {
gap: 8px;
bottom: 15px;
left: 15px;
right: 15px;
transform: translateX(0);
width: auto;
}
.control-button {
padding: 8px 12px;
font-size: 11px;
flex-grow: 1;
}
.stats {
top: 15px;
right: 15px;
padding: 8px 12px;
}
}
In conclusion, building an Interactive 3D Galaxy Animation using HTML, CSS, and JavaScript has been an exciting and visually rewarding project. By combining structure, styling, and animation logic, we’ve created an immersive experience that feels alive and dynamic🚀💫
If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!
