Files
BlackFruit-UI/js/globe.js
yiqiu 45608bbdd1
All checks were successful
continuous-integration/drone/push Build is passing
2
2025-11-21 20:26:09 +08:00

185 lines
5.5 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.
// 3D 地球效果(首页 banner 右侧)
// 依赖Three.js全局 THREE 对象),脚本路径建议:/web/BlackFruit-web/vender/three/three.min.js
(function () {
function initGlobe() {
var container = document.getElementById("bannerGlobe");
if (!container) return;
if (typeof THREE === "undefined") {
// Three.js 未加载时直接跳过,不影响其它功能
return;
}
var width = container.clientWidth || 400;
var height = container.clientHeight || 400;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
var radius = 1.2;
camera.position.set(0, 0, radius * 3.2);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setSize(width, height);
renderer.setClearColor(0x000000, 0); // 透明背景,融入页面
container.appendChild(renderer.domElement);
// 光照:柔和的环境光 + 方向光
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
var dirLight = new THREE.DirectionalLight(0xffffff, 0.9);
dirLight.position.set(5, 3, 5);
scene.add(dirLight);
var globeGroup = new THREE.Group();
scene.add(globeGroup);
// 地球球体
var earthGeometry = new THREE.SphereGeometry(radius, 64, 64);
// 纯色高光材质,若后续有贴图可替换为 MeshPhongMaterial + map
var earthMaterial = new THREE.MeshPhongMaterial({
color: 0x163a5f,
emissive: 0x061727,
shininess: 35,
specular: 0x3aaefc,
});
var earthMesh = new THREE.Mesh(earthGeometry, earthMaterial);
globeGroup.add(earthMesh);
// 大气层发光效果
var atmosphereGeometry = new THREE.SphereGeometry(radius * 1.06, 64, 64);
var atmosphereMaterial = new THREE.MeshBasicMaterial({
color: 0x2b9fff,
transparent: true,
opacity: 0.25,
side: THREE.BackSide,
});
var atmosphereMesh = new THREE.Mesh(
atmosphereGeometry,
atmosphereMaterial
);
globeGroup.add(atmosphereMesh);
// 将经纬度转换为球面坐标
function latLngToVector3(lat, lng, r) {
var phi = (90 - lat) * (Math.PI / 180);
var theta = (lng + 180) * (Math.PI / 180);
var x = -r * Math.sin(phi) * Math.cos(theta);
var z = r * Math.sin(phi) * Math.sin(theta);
var y = r * Math.cos(phi);
return new THREE.Vector3(x, y, z);
}
// 一些示例节点(可以根据业务改为真实机房经纬度)
var nodeConfigs = [
{ lat: 39.9, lng: 116.4 }, // 北京
{ lat: 31.2, lng: 121.5 }, // 上海
{ lat: 22.5, lng: 114.1 }, // 香港
{ lat: 1.3, lng: 103.8 }, // 新加坡
{ lat: 37.8, lng: -122.4 }, // 旧金山
{ lat: 52.5, lng: 13.4 }, // 柏林
{ lat: 35.7, lng: 139.7 }, // 东京
];
var nodeGroup = new THREE.Group();
globeGroup.add(nodeGroup);
var nodeMaterial = new THREE.MeshBasicMaterial({
color: 0x5ad8ff,
});
var nodes = [];
var nodeRadius = radius * 0.03;
nodeConfigs.forEach(function (cfg, index) {
var pos = latLngToVector3(cfg.lat, cfg.lng, radius * 1.02);
var geom = new THREE.SphereGeometry(nodeRadius, 16, 16);
var mesh = new THREE.Mesh(geom, nodeMaterial);
mesh.position.copy(pos);
nodeGroup.add(mesh);
nodes.push({
mesh: mesh,
baseScale: 1,
phase: Math.random() * Math.PI * 2 + index,
});
});
// 通过 Points 实现星空点阵背景
var starGeometry = new THREE.BufferGeometry();
var starCount = 600;
var starPositions = new Float32Array(starCount * 3);
for (var i = 0; i < starCount; i++) {
var sr = radius * 4 + Math.random() * radius * 2;
var theta = Math.random() * Math.PI * 2;
var phi = Math.acos(2 * Math.random() - 1);
var sx = sr * Math.sin(phi) * Math.cos(theta);
var sy = sr * Math.sin(phi) * Math.sin(theta);
var sz = sr * Math.cos(phi);
starPositions[i * 3] = sx;
starPositions[i * 3 + 1] = sy;
starPositions[i * 3 + 2] = sz;
}
starGeometry.setAttribute(
"position",
new THREE.BufferAttribute(starPositions, 3)
);
var starMaterial = new THREE.PointsMaterial({
color: 0x3f76ff,
size: 0.02,
transparent: true,
opacity: 0.8,
});
var stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
// 自适应窗口大小
function onResize() {
if (!container) return;
var w = container.clientWidth || width;
var h = container.clientHeight || height;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
window.addEventListener("resize", onResize);
// 动画循环
var lastTime = performance.now();
function animate() {
requestAnimationFrame(animate);
var now = performance.now();
var delta = (now - lastTime) / 1000;
lastTime = now;
// 地球缓慢自转
globeGroup.rotation.y += delta * 0.25;
// 星辰慢速旋转,增强空间感
stars.rotation.y -= delta * 0.05;
// 节点呼吸效果
var t = now / 1000;
nodes.forEach(function (n) {
var s = n.baseScale * (1 + 0.6 * Math.sin(t * 2.5 + n.phase));
n.mesh.scale.set(s, s, s);
});
renderer.render(scene, camera);
}
animate();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initGlobe);
} else {
initGlobe();
}
})();