Files
BaiTu-homepage/static/js/galaxy.js
2026-06-05 19:02:52 +08:00

1040 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ==================== 星空预览功能 ====================
(function() {
// DOM 元素
const modeSwitch = document.getElementById('mode-switch');
const photoAlbumModal = document.getElementById('photo-album-full-modal');
const photoAlbumBody = document.querySelector('.photo-album-body');
const galaxyCanvasWrapper = document.getElementById('galaxy-canvas-wrapper');
const galaxyCanvas = document.getElementById('galaxy-canvas');
const guestbookHeader = document.querySelector('#photo-album-full-modal .guestbook-header');
// Three.js 变量
let scene, camera, renderer;
let stars = [];
let galaxyGroups = []; // 多个银河系组
let mouse = { x: 0, y: 0, isDragging: false };
let targetRotation = { x: 0, y: 0 };
let currentRotation = { x: 0, y: 0 };
let cameraDistance = 1000;
let cameraTargetDistance = 1000;
let photoData = [];
let raycaster, mousePosition;
let hoveredStar = null;
let tooltip = null;
let isGalaxyMode = false;
let isWaterfallMode = false;
let lastMode = 'galaxy'; // 保存用户最后选择的模式
let animationId = null;
let currentLightboxIndex = -1;
let galaxyRotations = []; // 多个银河系的自旋角度
// 初始化
function init() {
if (!modeSwitch || !photoAlbumModal) return;
// 绑定事件
modeSwitch.addEventListener('click', toggleMode);
// 绑定模式标签点击事件
document.querySelectorAll('.mode-label').forEach(label => {
label.addEventListener('click', (e) => {
e.stopPropagation();
const mode = label.dataset.mode;
switchMode(mode);
});
});
// 创建提示元素
createTooltip();
// 页面加载完成后,检查弹窗状态并初始化模式
checkAndInitMode();
}
// 检查并初始化模式
function checkAndInitMode() {
// 如果弹窗已经打开,初始化星空模式
if (photoAlbumModal.classList.contains('active')) {
initDefaultMode();
} else {
// 否则等待弹窗打开
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
if (photoAlbumModal.classList.contains('active')) {
initDefaultMode();
observer.disconnect(); // 只执行一次
}
}
});
});
observer.observe(photoAlbumModal, {
attributes: true,
attributeFilter: ['class']
});
}
}
// 初始化默认模式
async function initDefaultMode() {
if (isGalaxyMode || isWaterfallMode) return; // 已经激活了模式
// 切换到用户最后选择的模式(默认星空模式)
await switchMode(lastMode);
}
// 创建提示元素
function createTooltip() {
tooltip = document.createElement('div');
tooltip.className = 'star-tooltip';
tooltip.innerHTML = `
<img src="" alt="star-preview">
<p></p>
`;
document.body.appendChild(tooltip);
}
// 切换模式
function toggleMode() {
if (isGalaxyMode) {
// 当前是星空模式,切换到瀑布模式
switchMode('waterfall');
} else if (isWaterfallMode) {
// 当前是瀑布模式,切换到星空模式
switchMode('galaxy');
} else {
// 默认打开星空模式
switchMode('galaxy');
}
}
// 切换到指定模式
async function switchMode(mode) {
// 保存用户选择的模式
lastMode = mode;
// 关闭所有模式
closeAllModes();
// 加载照片数据
await loadPhotoData();
// 更新UI状态
document.querySelectorAll('.mode-label').forEach(label => {
label.classList.remove('active');
if (label.dataset.mode === mode) {
label.classList.add('active');
}
});
// 切换header样式
if (guestbookHeader) {
if (mode === 'galaxy') {
guestbookHeader.classList.add('galaxy-mode-header');
} else {
guestbookHeader.classList.remove('galaxy-mode-header');
}
}
if (mode === 'galaxy') {
// 打开星空模式
photoAlbumBody.style.display = 'none';
galaxyCanvasWrapper.classList.add('active');
// 初始化Three.js场景
initScene();
// 创建星空
createGalaxy();
// 开始动画
animate();
isGalaxyMode = true;
} else if (mode === 'waterfall') {
// 瀑布模式 - 使用原来的photo-album-body
// photo-album-body已经默认显示不需要额外操作
isWaterfallMode = true;
}
}
// 关闭所有模式
function closeAllModes() {
// 关闭星空模式
if (isGalaxyMode) {
galaxyCanvasWrapper.classList.remove('active');
cleanupScene();
isGalaxyMode = false;
}
// 关闭瀑布模式
if (isWaterfallMode) {
isWaterfallMode = false;
}
// 恢复照片显示
photoAlbumBody.style.display = '';
// 隐藏提示
if (tooltip) {
tooltip.classList.remove('visible');
}
}
// 加载照片数据
async function loadPhotoData() {
try {
const response = await fetch('./static/img/photos/photos.json');
if (!response.ok) {
throw new Error('Failed to load photos.json');
}
const data = await response.json();
const photoFiles = data.photos || [];
photoData = photoFiles.map((filename, index) => ({
id: index + 1,
src: `./static/img/photos/${encodeURIComponent(filename)}`,
filename: filename
}));
} catch (error) {
console.error('加载照片失败:', error);
photoData = [];
}
}
// 初始化Three.js场景
function initScene() {
// 创建场景
scene = new THREE.Scene();
// 创建多个银河系组
galaxyGroups = [];
galaxyRotations = [];
// 创建9个银河系合理距离多种角度大胆倾斜保持足够距离
const galaxyPositions = [
// 中心主银河系 - 横着正常显示
{ x: 0, y: 0, z: 0, scale: 1.0, rotX: 0, rotZ: 0 },
// 前面银河系z正方向- 距离主银河系1200+
{ x: 900, y: 350, z: 1100, scale: 0.7, rotX: -0.4, rotZ: 0.6 },
{ x: -800, y: 600, z: 1000, scale: 0.65, rotX: 0.5, rotZ: -0.3 },
{ x: 600, y: -500, z: 1200, scale: 0.6, rotX: -0.7, rotZ: 0.4 },
// 近距离1300-1600单位
{ x: 1300, y: 400, z: -900, scale: 0.75, rotX: 0.8, rotZ: -0.5 },
{ x: -1200, y: -600, z: -800, scale: 0.7, rotX: -0.3, rotZ: 0.7 },
// 中距离1800-2200单位
{ x: 2000, y: 800, z: -1700, scale: 0.6, rotX: 0.6, rotZ: -0.8 },
{ x: -1900, y: 700, z: -1600, scale: 0.55, rotX: -0.5, rotZ: 0.4 },
{ x: 700, y: -1600, z: -1400, scale: 0.65, rotX: 0.9, rotZ: -0.6 },
];
galaxyPositions.forEach((pos, index) => {
const galaxyGroup = new THREE.Group();
galaxyGroup.position.set(pos.x, pos.y, pos.z);
galaxyGroup.scale.set(pos.scale, pos.scale, pos.scale);
// 设置不同的角度
galaxyGroup.rotation.x = pos.rotX;
galaxyGroup.rotation.z = pos.rotZ;
scene.add(galaxyGroup);
galaxyGroups.push(galaxyGroup);
galaxyRotations.push(0);
});
// 获取容器尺寸
const containerRect = galaxyCanvasWrapper.getBoundingClientRect();
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
containerRect.width / containerRect.height,
1,
5000 // 调整远裁剪面
);
camera.position.z = cameraDistance;
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: galaxyCanvas,
antialias: true,
alpha: true
});
renderer.setSize(containerRect.width, containerRect.height);
renderer.setPixelRatio(window.devicePixelRatio);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 添加点光源
const pointLight = new THREE.PointLight(0xFFD700, 1, 5000);
pointLight.position.set(0, 0, 1000);
scene.add(pointLight);
// 创建射线检测器
raycaster = new THREE.Raycaster();
mousePosition = new THREE.Vector2();
// 绑定事件
bindEvents();
}
// 创建星空
function createGalaxy() {
// 为每个银河系创建星星
galaxyGroups.forEach((galaxyGroup, index) => {
if (index === 0) {
// 主银河系 - 有照片星星
if (photoData.length > 0) {
photoData.forEach((photo, i) => {
createPhotoStar(photo, i, galaxyGroup);
});
}
// 添加装饰性星星
addDecorativeStars(galaxyGroup, 1, index);
} else {
// 其他银河系 - 只有装饰性星星,使用不同的配色
addDecorativeStars(galaxyGroup, 0.7, index); // 稍微小一些,传入银河系索引
}
});
}
// 创建照片星星
function createPhotoStar(photo, index, galaxyGroup) {
// 创建圆形纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(photo.src, (loadedTexture) => {
// 图片加载完成后,根据原始比例调整几何体
const image = loadedTexture.image;
const aspectRatio = image.width / image.height;
let width = 30;
let height = 30;
if (aspectRatio > 1) {
// 宽图宽度为30高度按比例
height = width / aspectRatio;
} else {
// 高图或正方形高度为30宽度按比例
height = 30;
width = height * aspectRatio;
}
// 更新几何体尺寸
star.geometry.dispose();
star.geometry = new THREE.PlaneGeometry(width, height);
hitArea.geometry.dispose();
hitArea.geometry = new THREE.PlaneGeometry(width * 2, height * 2);
});
// 创建默认正方形几何体(加载完成后会被替换)
const geometry = new THREE.PlaneGeometry(30, 30);
// 创建材质 - 无滤镜
const material = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
opacity: 0.95,
side: THREE.DoubleSide
});
// 创建网格
const star = new THREE.Mesh(geometry, material);
// 创建一个更大的透明点击检测区域
const hitGeometry = new THREE.PlaneGeometry(60, 60);
const hitMaterial = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0.001, // 几乎透明但不完全透明以便raycaster能检测到
side: THREE.DoubleSide
});
const hitArea = new THREE.Mesh(hitGeometry, hitMaterial);
// 随机位置(银河系分布)
const radius = 200 + Math.random() * 600;
const theta = Math.random() * Math.PI * 2;
const phi = (Math.random() - 0.5) * Math.PI * 0.3; // 扁平分布
const x = radius * Math.cos(theta) * Math.cos(phi);
const y = radius * Math.sin(phi) * 0.3; // 更扁平
const z = radius * Math.sin(theta) * Math.cos(phi);
// 设置两个网格的相同位置
star.position.set(x, y, z);
hitArea.position.set(x, y, z);
// 存储照片信息两个网格共享相同的userData
const userData = {
photoId: photo.id,
photoSrc: photo.src,
photoFilename: photo.filename
};
star.userData = userData;
hitArea.userData = userData;
// 面向相机
star.lookAt(camera.position);
hitArea.lookAt(camera.position);
// 将点击检测区域添加到组中
galaxyGroup.add(hitArea);
galaxyGroup.add(star);
stars.push(star);
stars.push(hitArea); // 也添加到检测数组中
}
// 创建默认星星
function createDefaultStars() {
for (let i = 0; i < 100; i++) {
const geometry = new THREE.CircleGeometry(2, 32);
const material = new THREE.MeshBasicMaterial({
color: 0xFFD700,
transparent: true,
opacity: 0.7
});
const star = new THREE.Mesh(geometry, material);
// 随机位置
const radius = 100 + Math.random() * 800;
const theta = Math.random() * Math.PI * 2;
const phi = (Math.random() - 0.5) * Math.PI;
star.position.x = radius * Math.cos(theta) * Math.cos(phi);
star.position.y = radius * Math.sin(phi);
star.position.z = radius * Math.sin(theta) * Math.cos(phi);
galaxyGroup.add(star);
stars.push(star);
}
}
// 添加装饰性星星(银河系效果)
function addDecorativeStars(galaxyGroup, scale = 1, galaxyIndex = 0) {
const starCount = Math.floor(8000 * scale); // 星星数量增加到8000
const starGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(starCount * 3);
const colors = new Float32Array(starCount * 3);
const sizes = new Float32Array(starCount);
// 银河系参数
const bulgeRadius = 150; // 银核半径
const diskRadius = 600; // 银盘半径
const armCount = galaxyIndex === 0 ? 4 : (galaxyIndex % 2 === 0 ? 4 : 6); // 主银河4条其他4或6条
const spin = 0.3; // 旋臂旋转角度
const radialRandomness = 0.4; // 径向随机性
// 根据银河系索引选择配色方案(主银河系用蓝色调)
const colorScheme = galaxyIndex === 0 ? 'blue' : getColorScheme(galaxyIndex);
// 获取银河系配色方案
function getColorScheme(index) {
const schemes = [
'blue', // 蓝色系
'purple', // 紫色系
'cyan', // 青色系
'pink', // 粉色系
'red', // 红色系
'green', // 绿色系
'orange', // 橙色系
'white', // 白色系
];
return schemes[(index - 1) % schemes.length];
}
// 淡化颜色(向白色靠拢)
function fadeColor(color, fadeAmount = 0.4) {
return [
color[0] + (1 - color[0]) * fadeAmount,
color[1] + (1 - color[1]) * fadeAmount,
color[2] + (1 - color[2]) * fadeAmount
];
}
// 根据配色方案获取颜色
function getColor(scheme, radiusRatio) {
let color;
const t = radiusRatio; // 0=中心1=边缘
switch (scheme) {
case 'warm': // 主银河系 - 暖黄色
return [1, 0.9 - t * 0.3, 0.6 - t * 0.4]; // 橙到紫
case 'blue':
color = [0.3, 0.5 + t * 0.3, 0.9 - t * 0.2];
return fadeColor(color, 0.45);
case 'purple':
color = [0.7 - t * 0.3, 0.3, 0.9 - t * 0.1];
return fadeColor(color, 0.45);
case 'cyan':
color = [0.2, 0.8 - t * 0.3, 0.9 - t * 0.2];
return fadeColor(color, 0.45);
case 'pink':
color = [0.9 - t * 0.2, 0.4 + t * 0.1, 0.6 - t * 0.2];
return fadeColor(color, 0.45);
case 'red':
color = [0.9 - t * 0.2, 0.3 + t * 0.1, 0.3];
return fadeColor(color, 0.45);
case 'green':
color = [0.3, 0.8 - t * 0.2, 0.4 + t * 0.1];
return fadeColor(color, 0.45);
case 'orange':
color = [0.9 - t * 0.2, 0.6 - t * 0.3, 0.2 + t * 0.1];
return fadeColor(color, 0.45);
case 'white':
color = [0.9 - t * 0.2, 0.9 - t * 0.2, 0.9 - t * 0.2];
return fadeColor(color, 0.3);
default:
return [1, 0.9 - t * 0.3, 0.6 - t * 0.4];
}
}
// 随机极坐标(用于径向偏移)
function getRandomPolarCoordinate(radius) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.random() * Math.PI * 2;
return {
x: radius * Math.sin(theta) * Math.cos(phi),
y: radius * Math.sin(theta) * Math.sin(phi),
z: radius * Math.cos(theta)
};
}
for (let i = 0; i < starCount; i++) {
let x, y, z, brightness, color;
const rand = Math.random();
if (rand < 0.12) {
// ==================== 银核12%====================
// 非常明亮的圆球区域,厚实密集
const r = Math.pow(Math.random(), 0.3) * bulgeRadius;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
x = r * Math.sin(phi) * Math.cos(theta);
y = r * Math.cos(phi) * 0.6; // 稍微扁平
z = r * Math.sin(phi) * Math.sin(theta);
brightness = 0.85 + Math.pow(1 - r / bulgeRadius, 2) * 0.15;
sizes[i] = 3 + Math.random() * 5; // 更大尺寸
// 银核颜色:暖白色
const bulgeColor = getColor(colorScheme, 0);
color = [
Math.min(1, bulgeColor[0] + 0.2),
Math.min(1, bulgeColor[1] + 0.15),
Math.min(1, bulgeColor[2] + 0.1)
];
} else if (rand < 0.30) {
// ==================== 旋臂上18%====================
// 参考 test.html 的旋臂算法
// 选择一条旋臂
const armIndex = i % armCount;
const branchAngle = (armIndex / armCount) * Math.PI * 2;
// 从银核向外的距离
const radius = bulgeRadius + Math.random() * (diskRadius - bulgeRadius);
// 旋臂旋转角度(距离越远旋转越多)
const spinAngle = spin * radius * 0.01;
// 径向随机偏移(让旋臂更粗壮)
const randRadius = Math.random() * radialRandomness * radius * 0.5;
const randOffset = getRandomPolarCoordinate(randRadius);
// 计算位置
x = radius * Math.cos(branchAngle + spinAngle) + randOffset.x;
z = radius * Math.sin(branchAngle + spinAngle) + randOffset.z;
y = randOffset.y * 0.3; // 扁平分布
// 亮度随距离衰减
const radiusRatio = (radius - bulgeRadius) / (diskRadius - bulgeRadius);
brightness = Math.max(0.5, 1 - radiusRatio * 0.4);
sizes[i] = 1.5 + Math.random() * 2.5;
// 颜色从内到外渐变
color = getColor(colorScheme, radiusRatio);
} else {
// ==================== 旋臂之间70%====================
// 随机分布在银盘上,不在旋臂上
// 从银核向外的距离
const radius = bulgeRadius + Math.random() * (diskRadius - bulgeRadius);
// 随机角度(不跟随旋臂)
const theta = Math.random() * Math.PI * 2;
// 随机偏移
const randOffset = getRandomPolarCoordinate(radius * 0.15);
// 计算位置
x = radius * Math.cos(theta) + randOffset.x;
z = radius * Math.sin(theta) + randOffset.z;
y = (Math.random() - 0.5) * 20; // 扁平分布
// 背景星星亮度提高,但不超过旋臂
const radiusRatio = (radius - bulgeRadius) / (diskRadius - bulgeRadius);
brightness = Math.max(0.45, 0.75 - radiusRatio * 0.3);
sizes[i] = 0.8 + Math.random() * 1.5; // 较小尺寸
// 颜色从内到外渐变,亮度提高但仍比旋臂暗
color = getColor(colorScheme, radiusRatio);
color[0] *= 0.75;
color[1] *= 0.75;
color[2] *= 0.75;
}
positions[i * 3] = x;
positions[i * 3 + 1] = y;
positions[i * 3 + 2] = z;
colors[i * 3] = color[0] * brightness;
colors[i * 3 + 1] = color[1] * brightness;
colors[i * 3 + 2] = color[2] * brightness;
sizes[i] *= brightness;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const starMaterial = new THREE.PointsMaterial({
size: 2.5,
vertexColors: true,
transparent: true,
opacity: 0.9,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const starField = new THREE.Points(starGeometry, starMaterial);
galaxyGroup.add(starField);
// 添加银核发光球体(使用不同的行星样式)
addGalacticCore(galaxyGroup, scale, galaxyIndex);
// 添加星云效果
addNebulaEffect(galaxyGroup, scale, galaxyIndex, colorScheme);
}
// 添加银核发光球体
function addGalacticCore(galaxyGroup, scale = 1, galaxyIndex = 0) {
if (galaxyIndex === 0) {
// 主银河系 - 使用地球纹理
const textureLoader = new THREE.TextureLoader();
const earthTexture = textureLoader.load('./static/img/planets/earth.jpg');
// 创建地球球体
const earthGeometry = new THREE.SphereGeometry(100, 64, 64);
const earthMaterial = new THREE.MeshBasicMaterial({
map: earthTexture,
transparent: true,
opacity: 0.95,
side: THREE.DoubleSide
});
const earthMesh = new THREE.Mesh(earthGeometry, earthMaterial);
galaxyGroup.add(earthMesh);
// 添加蓝色发光效果层
const glowGeometry = new THREE.SphereGeometry(110, 32, 32);
const glowMaterial = new THREE.MeshBasicMaterial({
color: 0x4169E1, // 蓝色光晕
transparent: true,
opacity: 0.3,
side: THREE.DoubleSide
});
const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);
galaxyGroup.add(glowMesh);
// 创建月球
const moonTexture = textureLoader.load('./static/img/planets/moon.jpg');
const moonGeometry = new THREE.SphereGeometry(25, 32, 32);
const moonMaterial = new THREE.MeshBasicMaterial({
map: moonTexture,
transparent: true,
opacity: 0.9,
side: THREE.DoubleSide
});
const moonMesh = new THREE.Mesh(moonGeometry, moonMaterial);
// 初始位置(离地球更远)
moonMesh.position.set(250, 0, 0);
galaxyGroup.add(moonMesh);
// 存储地球和月球引用用于动画
galaxyGroup.userData = {
earth: earthMesh,
earthRotation: 0,
moon: moonMesh,
moonAngle: 0,
moonRotation: 0
};
} else {
// 其他银河系 - 白色圆球
// 外层白色球体
const coreGeometry = new THREE.SphereGeometry(100, 32, 32);
const coreMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFFFF,
transparent: true,
opacity: 0.35,
side: THREE.DoubleSide
});
const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
galaxyGroup.add(coreMesh);
// 中层更亮
const innerCoreGeometry = new THREE.SphereGeometry(55, 32, 32);
const innerCoreMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFFFF,
transparent: true,
opacity: 0.55,
side: THREE.DoubleSide
});
const innerCoreMesh = new THREE.Mesh(innerCoreGeometry, innerCoreMaterial);
galaxyGroup.add(innerCoreMesh);
// 核心光点
const coreLightGeometry = new THREE.SphereGeometry(20, 32, 32);
const coreLightMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFFFF,
transparent: true,
opacity: 0.8,
side: THREE.DoubleSide
});
const coreLightMesh = new THREE.Mesh(coreLightGeometry, coreLightMaterial);
galaxyGroup.add(coreLightMesh);
}
}
// 添加星云效果
function addNebulaEffect(galaxyGroup, scale = 1, galaxyIndex = 0, colorScheme = 'blue') {
const nebulaCount = Math.floor(80 * scale); // 根据缩放调整数量
const armCount = galaxyIndex === 0 ? 4 : (galaxyIndex % 2 === 0 ? 4 : 6);
const nebulaGeometry = new THREE.BufferGeometry();
const nebulaPositions = new Float32Array(nebulaCount * 3);
const nebulaColors = new Float32Array(nebulaCount * 3);
const nebulaSizes = new Float32Array(nebulaCount);
// 根据配色方案获取星云基础颜色
function getNebulaBaseColor(scheme) {
switch (scheme) {
case 'blue': return [0.4, 0.6, 1.0];
case 'purple': return [0.7, 0.4, 1.0];
case 'cyan': return [0.3, 0.9, 1.0];
case 'pink': return [1.0, 0.5, 0.7];
case 'red': return [1.0, 0.4, 0.4];
case 'green': return [0.4, 0.9, 0.5];
case 'orange': return [1.0, 0.7, 0.3];
case 'white': return [0.9, 0.9, 0.9];
default: return [0.9, 0.7, 0.3];
}
}
const baseColor = getNebulaBaseColor(colorScheme);
for (let i = 0; i < nebulaCount; i++) {
// 主要分布在旋臂区域
const armIndex = Math.floor(Math.random() * armCount);
const armAngle = (armIndex / armCount) * Math.PI * 2;
const r = 150 + Math.pow(Math.random(), 0.3) * 500;
const spiralTightness = 0.12;
const theta = armAngle + spiralTightness * r;
const offset = (Math.random() - 0.5) * 60;
nebulaPositions[i * 3] = r * Math.cos(theta) + offset * Math.cos(theta + Math.PI / 2);
nebulaPositions[i * 3 + 1] = (Math.random() - 0.5) * 20;
nebulaPositions[i * 3 + 2] = r * Math.sin(theta) + offset * Math.sin(theta + Math.PI / 2);
// 使用银河系配色,带轻微随机变化
nebulaColors[i * 3] = baseColor[0] * (0.8 + Math.random() * 0.4);
nebulaColors[i * 3 + 1] = baseColor[1] * (0.8 + Math.random() * 0.4);
nebulaColors[i * 3 + 2] = baseColor[2] * (0.8 + Math.random() * 0.4);
nebulaSizes[i] = 15 + Math.random() * 25;
}
nebulaGeometry.setAttribute('position', new THREE.BufferAttribute(nebulaPositions, 3));
nebulaGeometry.setAttribute('color', new THREE.BufferAttribute(nebulaColors, 3));
const nebulaMaterial = new THREE.PointsMaterial({
size: 25,
vertexColors: true,
transparent: true,
opacity: 0.35, // 提高透明度
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const nebulaField = new THREE.Points(nebulaGeometry, nebulaMaterial);
galaxyGroup.add(nebulaField);
}
// 绑定事件
function bindEvents() {
// 鼠标按下
galaxyCanvas.addEventListener('mousedown', onMouseDown);
// 鼠标移动
galaxyCanvas.addEventListener('mousemove', onMouseMove);
// 鼠标释放
galaxyCanvas.addEventListener('mouseup', onMouseUp);
// 鼠标离开
galaxyCanvas.addEventListener('mouseleave', onMouseLeave);
// 滚轮缩放
galaxyCanvas.addEventListener('wheel', onMouseWheel);
// 点击星星
galaxyCanvas.addEventListener('click', onStarClick);
// 窗口大小变化
window.addEventListener('resize', onWindowResize);
}
// 鼠标按下
function onMouseDown(e) {
mouse.isDragging = true;
mouse.x = e.clientX;
mouse.y = e.clientY;
}
// 鼠标移动
function onMouseMove(e) {
if (mouse.isDragging) {
// 计算旋转角度
const deltaX = (e.clientX - mouse.x) * 0.005;
const deltaY = (e.clientY - mouse.y) * 0.005;
targetRotation.y += deltaX;
targetRotation.x += deltaY;
// 限制垂直旋转角度
targetRotation.x = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, targetRotation.x));
mouse.x = e.clientX;
mouse.y = e.clientY;
}
// 更新鼠标位置(用于射线检测)
mousePosition.x = (e.clientX / window.innerWidth) * 2 - 1;
mousePosition.y = -(e.clientY / window.innerHeight) * 2 + 1;
// 检测悬停的星星
checkHoveredStar(e);
}
// 鼠标释放
function onMouseUp() {
mouse.isDragging = false;
}
// 鼠标离开
function onMouseLeave() {
mouse.isDragging = false;
}
// 鼠标滚轮
function onMouseWheel(e) {
e.preventDefault();
// 调整相机距离
cameraTargetDistance += e.deltaY * 2;
cameraTargetDistance = Math.max(300, Math.min(3000, cameraTargetDistance));
}
// 点击星星
function onStarClick(e) {
// 更新鼠标位置
mousePosition.x = (e.clientX / window.innerWidth) * 2 - 1;
mousePosition.y = -(e.clientY / window.innerHeight) * 2 + 1;
// 射线检测
raycaster.setFromCamera(mousePosition, camera);
const intersects = raycaster.intersectObjects(stars);
if (intersects.length > 0) {
const clickedStar = intersects[0].object;
// 检查是否有照片数据
if (clickedStar.userData && clickedStar.userData.photoSrc) {
openStarLightbox(clickedStar.userData);
}
}
}
// 检测悬停的星星
function checkHoveredStar(e) {
raycaster.setFromCamera(mousePosition, camera);
const intersects = raycaster.intersectObjects(stars);
if (intersects.length > 0) {
const star = intersects[0].object;
if (star.userData && star.userData.photoSrc) {
hoveredStar = star;
galaxyCanvas.style.cursor = 'pointer';
// 显示提示
if (tooltip) {
tooltip.querySelector('img').src = star.userData.photoSrc;
tooltip.querySelector('p').textContent = `点击放大查看 #${star.userData.photoId}`;
tooltip.style.left = (e.clientX + 20) + 'px';
tooltip.style.top = (e.clientY - 40) + 'px';
tooltip.classList.add('visible');
}
}
} else {
hoveredStar = null;
galaxyCanvas.style.cursor = 'grab';
// 隐藏提示
if (tooltip) {
tooltip.classList.remove('visible');
}
}
}
// 打开星星放大查看器使用瀑布模式的lightbox
function openStarLightbox(starData) {
// 找到照片在数组中的索引
const photoIndex = photoData.findIndex(p => p.id === starData.photoId);
if (photoIndex === -1) return;
// 隐藏tooltip
if (tooltip) {
tooltip.classList.remove('visible');
}
// 调用瀑布模式的openPhotoLightbox函数
if (window.openPhotoLightbox) {
window.openPhotoLightbox(photoIndex);
}
}
// 窗口大小变化
function onWindowResize() {
if (camera && renderer && galaxyCanvasWrapper) {
const containerRect = galaxyCanvasWrapper.getBoundingClientRect();
camera.aspect = containerRect.width / containerRect.height;
camera.updateProjectionMatrix();
renderer.setSize(containerRect.width, containerRect.height);
}
}
// 动画循环
function animate() {
if (!galaxyCanvasWrapper.classList.contains('active')) return;
animationId = requestAnimationFrame(animate);
// 平滑旋转(用户拖拽)
currentRotation.x += (targetRotation.x - currentRotation.x) * 0.05;
currentRotation.y += (targetRotation.y - currentRotation.y) * 0.05;
// 银河系自旋(非常缓慢的旋转)
galaxyGroups.forEach((galaxyGroup, index) => {
galaxyRotations[index] += 0.0001 + index * 0.00005; // 不同的旋转速度
galaxyGroup.rotation.y = galaxyRotations[index];
// 月球围绕地球旋转
if (galaxyGroup.userData && galaxyGroup.userData.moon) {
galaxyGroup.userData.moonAngle += 0.001; // 公转速度(更慢)
galaxyGroup.userData.moonRotation += 0.005; // 自转速度(更慢)
const moonAngle = galaxyGroup.userData.moonAngle;
const moonRadius = 250; // 月球轨道半径(更远)
galaxyGroup.userData.moon.position.x = Math.cos(moonAngle) * moonRadius;
galaxyGroup.userData.moon.position.z = Math.sin(moonAngle) * moonRadius;
galaxyGroup.userData.moon.position.y = Math.sin(moonAngle * 0.5) * 30; // 轻微上下浮动
// 月球自转
galaxyGroup.userData.moon.rotation.y = galaxyGroup.userData.moonRotation;
}
// 地球自转
if (galaxyGroup.userData && galaxyGroup.userData.earth) {
galaxyGroup.userData.earthRotation += 0.002; // 地球自转速度(缓慢)
galaxyGroup.userData.earth.rotation.y = galaxyGroup.userData.earthRotation;
}
});
// 更新相机位置
const cameraX = cameraDistance * Math.sin(currentRotation.y) * Math.cos(currentRotation.x);
const cameraY = cameraDistance * Math.sin(currentRotation.x);
const cameraZ = cameraDistance * Math.cos(currentRotation.y) * Math.cos(currentRotation.x);
camera.position.set(cameraX, cameraY, cameraZ);
camera.lookAt(0, 0, 0);
// 平滑缩放
cameraDistance += (cameraTargetDistance - cameraDistance) * 0.05;
// 让照片星星面向相机
stars.forEach(star => {
if (star.userData && star.userData.photoSrc) {
star.lookAt(camera.position);
}
});
// 渲染场景
renderer.render(scene, camera);
}
// 清理场景
function cleanupScene() {
// 停止动画
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
// 移除所有对象
while (scene && scene.children.length > 0) {
const object = scene.children[0];
scene.remove(object);
if (object.geometry) object.geometry.dispose();
if (object.material) {
if (object.material.map) object.material.map.dispose();
object.material.dispose();
}
}
// 清空数组
stars = [];
galaxyGroups = [];
galaxyRotations = [];
// 移除渲染器
if (renderer) {
renderer.dispose();
renderer = null;
}
scene = null;
camera = null;
}
// DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();