// 首页 3D 粒子地球(banner 右侧) // 改造为:大量粒子从远处飞入,最终聚合成一个球体,并缓慢自转 // 依赖:全局 THREE(在 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; } var width = container.clientWidth || 400; var height = container.clientHeight || 400; // 基础 Three.js 场景 var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(55, width / height, 0.1, 1000); // 初始视角,稍微偏上俯视球体 camera.position.set(0, 0.8, 7.5); 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 PARTICLE_COUNT = 800; // 粒子数量,越多越实,但性能负担也越大 var SPHERE_RADIUS = 2.6; // 球体半径(世界单位) var SPHERE_OFFSET_X = 3.0; // 球心在 X 轴上的偏移量,用于将球体放在画布偏右的位置 var START_RADIUS_MIN = 10.0; // 粒子起始距离下限(调大一点,让更多粒子从画布边缘外飞入) var START_RADIUS_MAX = 18.0; // 粒子起始距离上限 var FORMATION_DURATION = 13000; // 粒子从散开到聚合成球的时间(毫秒) var ROTATION_SPEED = 0.25; // 成形后的自转速度(弧度/秒,约等于旧地球的速度) // 准备缓动函数(飞入时用 easeOutCubic,让粒子接近球面时减速) function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); } // 准备粒子数据:startPositions / targetPositions / current positions var geometry = new THREE.BufferGeometry(); var positions = new Float32Array(PARTICLE_COUNT * 3); var startPositions = new Float32Array(PARTICLE_COUNT * 3); var targetPositions = new Float32Array(PARTICLE_COUNT * 3); // 随机在球壳表面生成点(均匀分布),作为目标位置 function randomPointOnSphere(radius) { // 使用均匀球面采样:theta = 2πu, phi = arccos(2v - 1) var u = Math.random(); var v = Math.random(); var theta = 2 * Math.PI * u; var phi = Math.acos(2 * v - 1); var sinPhi = Math.sin(phi); var x = radius * sinPhi * Math.cos(theta); var y = radius * Math.cos(phi); var z = radius * sinPhi * Math.sin(theta); return { x: x, y: y, z: z }; } function randomPointFarFromCenter(minR, maxR) { var radius = minR + Math.random() * (maxR - minR); var u = Math.random(); var v = Math.random(); var theta = 2 * Math.PI * u; var phi = Math.acos(2 * v - 1); var sinPhi = Math.sin(phi); var x = radius * sinPhi * Math.cos(theta); var y = radius * Math.cos(phi); var z = radius * sinPhi * Math.sin(theta); return { x: x, y: y, z: z }; } for (var i = 0; i < PARTICLE_COUNT; i++) { var i3 = i * 3; var target = randomPointOnSphere(SPHERE_RADIUS); targetPositions[i3] = target.x; targetPositions[i3 + 1] = target.y; targetPositions[i3 + 2] = target.z; var start = randomPointFarFromCenter(START_RADIUS_MIN, START_RADIUS_MAX); startPositions[i3] = start.x; startPositions[i3 + 1] = start.y; startPositions[i3 + 2] = start.z; // 初始位置就是起始位置(粒子还未飞入) positions[i3] = start.x; positions[i3 + 1] = start.y; positions[i3 + 2] = start.z; } geometry.setAttribute( "position", new THREE.BufferAttribute(positions, 3) ); var material = new THREE.PointsMaterial({ color: 0x38bdf8, // 粒子主色(可自行调整) size: 0.08, // 每个粒子在世界中的尺寸 sizeAttenuation: true, transparent: true, opacity: 0.95, depthWrite: false, blending: THREE.AdditiveBlending, }); var particleSphere = new THREE.Points(geometry, material); scene.add(particleSphere); // 也可以增加一个非常淡的内层球体,进一步强调轮廓(如不需要可注释掉) var innerSphereGeom = new THREE.SphereGeometry(SPHERE_RADIUS * 0.98, 48, 48); var innerSphereMat = new THREE.MeshBasicMaterial({ color: 0x0f172a, transparent: true, opacity: 0.25, side: THREE.BackSide, }); var innerSphereMesh = new THREE.Mesh(innerSphereGeom, innerSphereMat); scene.add(innerSphereMesh); // 将球体整体向右偏移,让球体位于画布偏右侧,避免挡住左侧文案 particleSphere.position.x = SPHERE_OFFSET_X; innerSphereMesh.position.x = SPHERE_OFFSET_X; // 暴露给调试用 if (typeof window !== "undefined") { window.__particleGlobe = { scene: scene, camera: camera, renderer: renderer, particleSphere: particleSphere, }; } // 自适应窗口大小 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 startTime = performance.now(); var lastTime = startTime; function animate() { requestAnimationFrame(animate); var now = performance.now(); var delta = (now - lastTime) / 1000; var elapsed = now - startTime; lastTime = now; // 粒子从起始位置插值到球面目标位置 var t = Math.min(1, elapsed / FORMATION_DURATION); var eased = easeOutCubic(t); var i, i3; for (i = 0; i < PARTICLE_COUNT; i++) { i3 = i * 3; var sx = startPositions[i3]; var sy = startPositions[i3 + 1]; var sz = startPositions[i3 + 2]; var tx = targetPositions[i3]; var ty = targetPositions[i3 + 1]; var tz = targetPositions[i3 + 2]; positions[i3] = sx + (tx - sx) * eased; positions[i3 + 1] = sy + (ty - sy) * eased; positions[i3 + 2] = sz + (tz - sz) * eased; } geometry.attributes.position.needsUpdate = true; // 整体自转 particleSphere.rotation.y += ROTATION_SPEED * delta; innerSphereMesh.rotation.y += ROTATION_SPEED * delta * 0.9; renderer.render(scene, camera); } animate(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initGlobe); } else { initGlobe(); } })();