Files
BlackFruit-UI/js/globe.js
yiqiu 535409dfcf
All checks were successful
continuous-integration/drone/push Build is passing
eee
2025-11-21 23:10:13 +08:00

174 lines
5.6 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);
// 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);
// 告诉 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);
}
});
}
// 按 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: "#FF6A00",
transparent: true,
opacity: 0.9,
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;
}
// 自适应窗口大小
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;
renderer.render(scene, camera);
}
animate();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initGlobe);
} else {
initGlobe();
}
})();