// 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 ***!