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

949 lines
34 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正方向
{ x: 500, y: 200, z: 600, scale: 0.7, rotX: 0.1, rotZ: -0.05 },
{ x: -400, y: 350, z: 500, scale: 0.65, rotX: -0.08, rotZ: 0.12 },
{ x: 300, y: -250, z: 700, scale: 0.6, rotX: 0.15, rotZ: -0.1 },
// 近距离600-900单位
{ x: 700, y: 200, z: -500, scale: 0.75, rotX: 0.1, rotZ: -0.05 },
{ x: -600, y: -350, z: -400, scale: 0.7, rotX: -0.08, rotZ: 0.12 },
// 中距离1000-1500单位
{ x: 1200, y: 500, z: -1000, scale: 0.6, rotX: 0.15, rotZ: -0.1 },
{ x: -1100, y: 400, z: -900, scale: 0.55, rotX: -0.12, rotZ: 0.08 },
{ x: 400, y: -900, z: -800, scale: 0.65, rotX: 0.05, rotZ: 0.15 },
];
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);
// 创建正方形几何体
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 radius = 200 + Math.random() * 600;
const theta = Math.random() * Math.PI * 2;
const phi = (Math.random() - 0.5) * Math.PI * 0.3; // 扁平分布
star.position.x = radius * Math.cos(theta) * Math.cos(phi);
star.position.y = radius * Math.sin(phi) * 0.3; // 更扁平
star.position.z = radius * Math.sin(theta) * Math.cos(phi);
// 存储照片信息
star.userData = {
photoId: photo.id,
photoSrc: photo.src,
photoFilename: photo.filename
};
// 面向相机
star.lookAt(camera.position);
galaxyGroup.add(star);
stars.push(star);
}
// 创建默认星星
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(4000 * scale); // 根据缩放调整星星数量
const starGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(starCount * 3);
const colors = new Float32Array(starCount * 3);
const sizes = new Float32Array(starCount);
// 银河系参数
const bulgeRadius = 120; // 银核半径
const diskRadius = 700; // 银盘半径
const armCount = 4; // 旋臂数量
const spiralAngle = 12; // 螺旋角(度)
const spiralTightness = 0.005; // 螺旋紧密度
// 根据银河系索引选择配色方案
const colorScheme = galaxyIndex === 0 ? 'warm' : 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, brightness, distanceFromCenter) {
let color;
switch (scheme) {
case 'warm': // 主银河系 - 暖黄色(不淡化)
if (distanceFromCenter < 0.3) {
color = [1, 0.9 + brightness * 0.1, 0.7 + brightness * 0.3];
} else if (distanceFromCenter < 0.5) {
color = [1, 0.8 + Math.random() * 0.15, 0.4 + Math.random() * 0.3];
} else {
color = [0.9 + Math.random() * 0.1, 0.85 + Math.random() * 0.1, 0.6 + Math.random() * 0.2];
}
return color; // 主银河系不淡化
case 'blue': // 蓝色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.3, 0.5 + brightness * 0.3, 0.9 + brightness * 0.1];
} else if (distanceFromCenter < 0.5) {
color = [0.2 + Math.random() * 0.1, 0.4 + Math.random() * 0.2, 0.8 + Math.random() * 0.15];
} else {
color = [0.1 + Math.random() * 0.15, 0.3 + Math.random() * 0.2, 0.7 + Math.random() * 0.2];
}
return fadeColor(color, 0.45);
case 'purple': // 紫色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.7 + brightness * 0.3, 0.3, 0.9 + brightness * 0.1];
} else if (distanceFromCenter < 0.5) {
color = [0.6 + Math.random() * 0.2, 0.2 + Math.random() * 0.1, 0.8 + Math.random() * 0.15];
} else {
color = [0.5 + Math.random() * 0.2, 0.1 + Math.random() * 0.15, 0.7 + Math.random() * 0.2];
}
return fadeColor(color, 0.45);
case 'cyan': // 青色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.2, 0.8 + brightness * 0.2, 0.9 + brightness * 0.1];
} else if (distanceFromCenter < 0.5) {
color = [0.1 + Math.random() * 0.1, 0.7 + Math.random() * 0.15, 0.8 + Math.random() * 0.15];
} else {
color = [0.05 + Math.random() * 0.1, 0.6 + Math.random() * 0.2, 0.7 + Math.random() * 0.2];
}
return fadeColor(color, 0.45);
case 'pink': // 粉色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.9 + brightness * 0.1, 0.4 + brightness * 0.2, 0.6 + brightness * 0.2];
} else if (distanceFromCenter < 0.5) {
color = [0.8 + Math.random() * 0.15, 0.3 + Math.random() * 0.15, 0.5 + Math.random() * 0.2];
} else {
color = [0.7 + Math.random() * 0.2, 0.2 + Math.random() * 0.15, 0.4 + Math.random() * 0.2];
}
return fadeColor(color, 0.45);
case 'red': // 红色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.9 + brightness * 0.1, 0.3, 0.3];
} else if (distanceFromCenter < 0.5) {
color = [0.8 + Math.random() * 0.15, 0.2 + Math.random() * 0.1, 0.2 + Math.random() * 0.1];
} else {
color = [0.7 + Math.random() * 0.2, 0.1 + Math.random() * 0.15, 0.1 + Math.random() * 0.15];
}
return fadeColor(color, 0.45);
case 'green': // 绿色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.3, 0.8 + brightness * 0.2, 0.4];
} else if (distanceFromCenter < 0.5) {
color = [0.2 + Math.random() * 0.1, 0.7 + Math.random() * 0.15, 0.3 + Math.random() * 0.1];
} else {
color = [0.1 + Math.random() * 0.15, 0.6 + Math.random() * 0.2, 0.2 + Math.random() * 0.15];
}
return fadeColor(color, 0.45);
case 'orange': // 橙色系(淡化)
if (distanceFromCenter < 0.3) {
color = [0.9 + brightness * 0.1, 0.6 + brightness * 0.2, 0.2];
} else if (distanceFromCenter < 0.5) {
color = [0.8 + Math.random() * 0.15, 0.5 + Math.random() * 0.15, 0.15 + Math.random() * 0.1];
} else {
color = [0.7 + Math.random() * 0.2, 0.4 + Math.random() * 0.2, 0.1 + Math.random() * 0.15];
}
return fadeColor(color, 0.45);
case 'white': // 白色系(已经是淡色,稍微淡化)
if (distanceFromCenter < 0.3) {
color = [0.9 + brightness * 0.1, 0.9 + brightness * 0.1, 0.9 + brightness * 0.1];
} else if (distanceFromCenter < 0.5) {
color = [0.8 + Math.random() * 0.1, 0.8 + Math.random() * 0.1, 0.8 + Math.random() * 0.1];
} else {
color = [0.7 + Math.random() * 0.15, 0.7 + Math.random() * 0.15, 0.7 + Math.random() * 0.15];
}
return fadeColor(color, 0.3); // 白色系淡化程度低一些
default:
color = [1, 1, 1];
return color;
}
}
for (let i = 0; i < starCount; i++) {
let x, y, z, brightness;
// 根据概率分配
const rand = Math.random();
if (rand < 0.15) {
// ==================== 银核15%====================
// 非常明亮的圆球区域,大量星星密集分布
// 使用球形分布,中心密度最高
const r = Math.pow(Math.random(), 0.2) * 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.5; // 稍微扁平一些
z = r * Math.sin(phi) * Math.sin(theta);
// 银核非常亮,中心最亮
brightness = 0.85 + Math.pow(1 - r / bulgeRadius, 2) * 0.15;
sizes[i] = 3 + Math.random() * 4; // 大尺寸的明亮星星
} else if (rand < 0.85) {
// ==================== 旋臂70%====================
// 星星严格分布在旋臂上
// 选择一条旋臂
const armIndex = Math.floor(Math.random() * armCount);
const armBaseAngle = (armIndex / armCount) * Math.PI * 2;
// 从银核向外延伸的距离
const distance = bulgeRadius + Math.pow(Math.random(), 0.6) * (diskRadius - bulgeRadius);
// 螺旋线角度
const spiralTheta = armBaseAngle + spiralTightness * distance * Math.log(1 + distance / bulgeRadius);
// 旋臂宽度(更粗壮)
const armWidth = 880 + distance * 0.12;
// 高斯分布让星星集中在旋臂中心
const gaussianRandom = (Math.random() + Math.random() + Math.random()) / 3 - 0.5;
const offset = gaussianRandom * armWidth * 0.5;
// 计算位置
const angle = spiralTheta;
x = distance * Math.cos(angle) + offset * Math.cos(angle + Math.PI / 2);
z = distance * Math.sin(angle) + offset * Math.sin(angle + Math.PI / 2);
// 高度分布(扁平)
y = (Math.random() - 0.5) * 15 * (distance / diskRadius);
// 旋臂较亮
brightness = Math.max(0.6, 1 - distance / diskRadius * 0.3);
sizes[i] = 1.5 + Math.random() * 2;
} else {
// ==================== 背景星星30%====================
// 随机分布,但避开旋臂区域(相对稀疏)
const distance = bulgeRadius + Math.random() * (diskRadius - bulgeRadius);
const theta = Math.random() * Math.PI * 2;
x = distance * Math.cos(theta);
z = distance * Math.sin(theta);
y = (Math.random() - 0.5) * 50; // 扁平分布
// 背景较暗
brightness = Math.max(0.2, 0.5 - distance / diskRadius * 0.3);
sizes[i] = 0.5 + Math.random() * 1;
}
positions[i * 3] = x;
positions[i * 3 + 1] = y;
positions[i * 3 + 2] = z;
// 设置颜色(根据配色方案)
const distanceFromCenter = Math.sqrt(x * x + y * y + z * z) / bulgeRadius;
const color = getColor(colorScheme, brightness, distanceFromCenter);
colors[i * 3] = color[0];
colors[i * 3 + 1] = color[1];
colors[i * 3 + 2] = color[2];
// 背景星星用冷色调(除了主银河系)
if (rand >= 0.70 && galaxyIndex !== 0) {
colors[i * 3] *= 0.5;
colors[i * 3 + 1] *= 0.5;
colors[i * 3 + 2] *= 0.5;
}
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);
// 添加星云效果
addNebulaEffect(galaxyGroup, scale);
}
// 添加银核发光球体
function addGalacticCore(galaxyGroup, scale = 1) {
// 创建一个明亮的发光球体作为银核
const coreGeometry = new THREE.SphereGeometry(80, 32, 32);
const coreMaterial = new THREE.MeshBasicMaterial({
color: 0xFFF5E1, // 暖白色
transparent: true,
opacity: 0.3,
side: THREE.DoubleSide
});
const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
galaxyGroup.add(coreMesh);
// 添加第二层发光(更亮更小)
const innerCoreGeometry = new THREE.SphereGeometry(40, 32, 32);
const innerCoreMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFBE6, // 更亮的白色
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide
});
const innerCoreMesh = new THREE.Mesh(innerCoreGeometry, innerCoreMaterial);
galaxyGroup.add(innerCoreMesh);
// 添加核心光点(最亮)
const coreLightGeometry = new THREE.SphereGeometry(15, 32, 32);
const coreLightMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFFF0, // 纯白偏黄
transparent: true,
opacity: 0.7,
side: THREE.DoubleSide
});
const coreLightMesh = new THREE.Mesh(coreLightGeometry, coreLightMaterial);
galaxyGroup.add(coreLightMesh);
}
// 添加星云效果
function addNebulaEffect(galaxyGroup, scale = 1) {
const nebulaCount = Math.floor(80 * scale); // 根据缩放调整数量
const nebulaGeometry = new THREE.BufferGeometry();
const nebulaPositions = new Float32Array(nebulaCount * 3);
const nebulaColors = new Float32Array(nebulaCount * 3);
const nebulaSizes = new Float32Array(nebulaCount);
for (let i = 0; i < nebulaCount; i++) {
// 主要分布在旋臂区域
const armIndex = Math.floor(Math.random() * 4);
const armAngle = (armIndex / 4) * 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] = 0.9 + Math.random() * 0.1;
nebulaColors[i * 3 + 1] = 0.7 + Math.random() * 0.2;
nebulaColors[i * 3 + 2] = 0.3 + Math.random() * 0.2;
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];
});
// 更新相机位置
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();
}
})();