Files
BlackFruit-UI/js/globe.js
yiqiu 60ebfede6c
All checks were successful
continuous-integration/drone/push Build is passing
修复图片路径问题:删除 /web/BlackFruit-web/ 前缀
- 批量删除所有文件中的 /web/BlackFruit-web/ 前缀
- 将相对路径改为绝对路径(以 / 开头)
- 修改范围:
  - /assets/ 资源路径
  - /css/ 样式文件路径
  - /js/ 脚本文件路径
  - /common/ 公共资源路径
  - /vender/ 第三方库路径
- 解决部署后所有静态资源 404 的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 15:40:46 +08:00

241 lines
8.1 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-globe
// 依赖:全局 THREE 和 ThreeGlobe在 index.html 中通过 CDN 引入)
(function () {
function initGlobe() {
var container = document.getElementById("bannerGlobe");
if (!container) {
console.warn("[Globe] bannerGlobe container not found");
return;
}
if (typeof THREE === "undefined") {
console.warn(
"[Globe] THREE is undefined, please ensure three.min.js is loaded"
);
return;
}
if (typeof ThreeGlobe === "undefined") {
console.warn(
"[Globe] ThreeGlobe is undefined, please ensure three-globe.min.js is loaded"
);
return;
}
var width = container.clientWidth || 400;
var height = container.clientHeight || 400;
// 基础 Three.js 场景
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(40, width / height, 0.1, 1000);
// 初始视角,具体距离在 globe 准备好后根据半径自动调整
camera.position.set(0, 0.2, 8.0);
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);
// 柔和光照
var ambient = new THREE.AmbientLight(0xffffff, 0.65);
scene.add(ambient);
var dirLight = new THREE.DirectionalLight(0xffffff, 0.9);
dirLight.position.set(5, 3, 5);
scene.add(dirLight);
// 节点数据示例(可替换为真实机房经纬度,至少 15 个)
var nodes = [
{ name: "Beijing", lat: 39.9, lng: 116.4 },
{ name: "Shanghai", lat: 31.2, lng: 121.5 },
{ name: "Guangzhou", lat: 23.1, lng: 113.3 },
{ name: "Shenzhen", lat: 22.5, lng: 114.1 },
{ name: "Hong Kong", lat: 22.3, lng: 114.2 },
{ name: "Singapore", lat: 1.3, lng: 103.8 },
{ name: "Tokyo", lat: 35.7, lng: 139.7 },
{ name: "Osaka", lat: 34.7, lng: 135.5 },
{ name: "Seoul", lat: 37.5, lng: 126.9 },
{ name: "Sydney", lat: -33.9, lng: 151.2 },
{ name: "Mumbai", lat: 19.0, lng: 72.8 },
{ name: "Frankfurt", lat: 50.1, lng: 8.7 },
{ name: "London", lat: 51.5, lng: -0.1 },
{ name: "Paris", lat: 48.9, lng: 2.4 },
{ name: "New York", lat: 40.7, lng: -74.0 },
{ name: "San Francisco", lat: 37.8, lng: -122.4 }
];
// three-globe 实例使用空心陆地多边形hollow globe 风格)
var globe = new ThreeGlobe({
waitForGlobeReady: true,
animateIn: true,
})
// 不使用 three-globe 内置球体贴图,只保留大气层,陆地轮廓由 polygons 层绘制
.showGlobe(false)
.showAtmosphere(false)
.atmosphereColor("#2b9fff")
.atmosphereAltitude(0.18)
.showGraticules(false);
scene.add(globe);
// 存放自定义“闪烁节点” mesh便于在动画循环中更新
var nodeGlows = [];
// 告诉 three-globe 当前渲染器的实际尺寸,避免默认按全窗口大小计算导致比例失真
if (typeof globe.rendererSize === "function") {
globe.rendererSize(new THREE.Vector2(width, height));
}
// 在 globe 初始化完成后,根据实际半径自动调整相机距离,保证整球落在视野内
if (typeof globe.onGlobeReady === "function" && typeof globe.getGlobeRadius === "function") {
globe.onGlobeReady(function () {
var r = globe.getGlobeRadius(); // three-globe 内部使用的球半径
// 经验:以半径的约 3 倍距离拍摄,配合 40° 视角,可以完整显示球体并留一定留白
var dist = r * 3.2;
camera.position.set(0, 0.2, dist);
camera.lookAt(0, 0, 0);
// 通知 three-globe 当前视角,用于部分图层的内部计算
if (typeof globe.setPointOfView === "function") {
globe.setPointOfView(camera);
}
// 使用 globe 的工具方法,将经纬度转换为球面上的 3D 坐标
var hasGetCoords = typeof globe.getCoords === "function";
nodes.forEach(function (n, idx) {
var pos;
if (hasGetCoords) {
// altitude 略高于球面,避免被多边形遮挡
pos = globe.getCoords(n.lat, n.lng, 0.02);
} else {
// 兼容降级:简单根据半径和经纬度计算
var phi = (90 - n.lat) * (Math.PI / 180);
var theta = (n.lng + 180) * (Math.PI / 180);
var rr = r * 1.02;
pos = {
x: -rr * Math.sin(phi) * Math.cos(theta),
z: rr * Math.sin(phi) * Math.sin(theta),
y: rr * Math.cos(phi)
};
}
var glowGeom = new THREE.SphereGeometry(r * 0.03, 16, 16);
var glowMat = new THREE.MeshBasicMaterial({
// 绿色闪烁点
color: "#22C55E",
transparent: true,
opacity: 0.7
});
var glowMesh = new THREE.Mesh(glowGeom, glowMat);
glowMesh.position.set(pos.x, pos.y, pos.z);
globe.add(glowMesh);
nodeGlows.push({
mesh: glowMesh,
// 随机相位,让节点闪烁不同步
phase: Math.random() * Math.PI * 2 + idx
});
});
});
}
// 按 hollow-globe 风格加载陆地多边形,绘制空心地球轮廓
(function loadLandPolygons() {
if (typeof topojson === "undefined") {
console.warn(
"[Globe] topojson-client is undefined, unable to render hollow globe polygons"
);
return;
}
// 请确保 assets/data/land-110m.json 存在
fetch("assets/data/land-110m.json")
.then(function (res) {
return res.json();
})
.then(function (landTopo) {
try {
var landGeo = topojson.feature(
landTopo,
landTopo.objects.land
).features;
globe
.polygonsData(landGeo)
.polygonCapMaterial(
new THREE.MeshLambertMaterial({
// 默认蓝色陆地,可按需自行调整
color: "#1D4ED8",
transparent: true,
opacity: 0.85,
side: THREE.DoubleSide,
})
)
.polygonSideColor(function () {
return "rgba(0,0,0,0)";
});
} catch (e) {
console.warn("[Globe] failed to parse land-110m topology:", e);
}
})
.catch(function (err) {
console.warn(
"[Globe] failed to load assets/data/land-110m.json:",
err
);
});
})();
// 暴露给调试用
if (typeof window !== "undefined") {
window.__globe = globe;
}
// 自适应窗口大小
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);
// 同步 three-globe 对 renderer 尺寸的感知,保证缩放比例一致
if (typeof globe.rendererSize === "function") {
globe.rendererSize(new THREE.Vector2(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;
// 地球自转
globe.rotation.y += delta * 0.25;
// 节点“呼吸式”闪烁
var t = now / 1000;
nodeGlows.forEach(function (n) {
var s = 0.7 + 0.3 * Math.sin(t * 3 + n.phase);
n.mesh.scale.set(s, s, s);
n.mesh.material.opacity = 0.4 + 0.4 * Math.sin(t * 3 + n.phase);
});
renderer.render(scene, camera);
}
animate();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initGlobe);
} else {
initGlobe();
}
})();*** End Patch ***!