添加星空相册

This commit is contained in:
DelLevin-Home
2026-06-05 02:13:42 +08:00
parent a664c423fa
commit 1668c84436
6 changed files with 1604 additions and 16 deletions

948
static/js/galaxy.js Normal file
View File

@@ -0,0 +1,948 @@
// ==================== 星空预览功能 ====================
(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();
}
})();