Files
BlackFruit-UI/js/globe.js
yiqiu 5c2a4311f7
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-22 18:54:57 +08:00

216 lines
7.4 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在 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 = 1200; // 粒子数量,越多越实,但性能负担也越大
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 = 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.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();
}
})();