Files
BlackFruit-UI/js/globe.js
yiqiu 37b33ef19c
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-21 22:46:27 +08:00

220 lines
6.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-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);
// 稍微偏右俯视一点,拉远一点避免球体被裁剪
camera.position.set(0, 0.3, 4.8);
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);
// 示例节点数据(可后续替换为真实机房经纬度)
var nodes = [
{ name: "Beijing", lat: 39.9, lng: 116.4 },
{ name: "Shanghai", lat: 31.2, lng: 121.5 },
{ name: "Hong Kong", lat: 22.5, lng: 114.1 },
{ name: "Singapore", lat: 1.3, lng: 103.8 },
{ name: "San Francisco", lat: 37.8, lng: -122.4 },
{ name: "Berlin", lat: 52.5, lng: 13.4 },
{ name: "Tokyo", lat: 35.7, lng: 139.7 },
];
// 示例 arcs从北京发出到其他节点
var arcs = nodes
.filter(function (n) {
return n.name !== "Beijing";
})
.map(function (n) {
return {
startLat: 39.9,
startLng: 116.4,
endLat: n.lat,
endLng: n.lng,
};
});
// three-globe 实例:使用大气层 + 节点 + 飞线 + 空心陆地多边形
var globe = new ThreeGlobe({
waitForGlobeReady: true,
animateIn: true,
})
// 不使用 three-globe 内置球体贴图,只保留大气层,陆地轮廓由 polygons 层绘制
.showGlobe(false)
.showAtmosphere(false)
.atmosphereColor("#2b9fff")
.atmosphereAltitude(0.18)
.showGraticules(false)
// 节点柱状体
.pointsData(nodes)
.pointColor(function () {
return "#5ad8ff";
})
.pointAltitude(0.06)
.pointRadius(0.32)
.pointsTransitionDuration(1000)
// 弧线
.arcsData(arcs)
.arcColor(function () {
return ["#5ad8ff", "#ffffff"];
})
.arcAltitude(0.35)
.arcStroke(0.45)
.arcDashLength(0.6)
.arcDashGap(0.25)
.arcDashAnimateTime(2600);
scene.add(globe);
// 按 hollow-globe 风格加载陆地多边形,绘制空心地球轮廓
(function loadLandPolygons() {
if (typeof topojson === "undefined") {
console.warn(
"[Globe] topojson-client is undefined, unable to render hollow globe polygons"
);
return;
}
// 请确保 /web/BlackFruit-web/assets/data/land-110m.json 存在,
// 内容为你刚才提供的 Topology JSON。
fetch("/web/BlackFruit-web/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: "darkslategrey",
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 /web/BlackFruit-web/assets/data/land-110m.json:",
err
);
});
})();
// 暴露给调试用
if (typeof window !== "undefined") {
window.__globe = globe;
}
// 星辰背景(简单加一点点空间感)
var starGeometry = new THREE.BufferGeometry();
var starCount = 600;
var starPositions = new Float32Array(starCount * 3);
for (var i = 0; i < starCount; i++) {
var sr = 10 + Math.random() * 5;
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.08,
transparent: true,
opacity: 0.7,
});
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;
// 地球自转
globe.rotation.y += delta * 0.25;
// 星空轻微旋转
stars.rotation.y -= delta * 0.03;
renderer.render(scene, camera);
}
animate();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initGlobe);
} else {
initGlobe();
}
})();