211 lines
7.0 KiB
JavaScript
211 lines
7.0 KiB
JavaScript
// 首页 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(40, 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 = 2200; // 粒子数量,越多越实,但性能负担也越大
|
||
var SPHERE_RADIUS = 2.4; // 球体半径(世界单位)
|
||
var START_RADIUS_MIN = 5.0; // 粒子起始距离下限
|
||
var START_RADIUS_MAX = 9.0; // 粒子起始距离上限
|
||
var FORMATION_DURATION = 4000; // 粒子从散开到聚合成球的时间(毫秒)
|
||
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.06, // 每个粒子在世界中的尺寸
|
||
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);
|
||
|
||
// 暴露给调试用
|
||
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();
|
||
}
|
||
})();
|