| name | globe-particles |
| description | 创建一个地球般的 3D 粒子可视化,带有密集发光的球形核心与更薄的轨道环或扁平圆盘。当设计需要用真实的 WebGL/Three.js 粒子渲染出高级的行星、轨道或合成数据地球效果时使用,而非普通的星空或整页布局变更。 |
Globe Particles
范围
- 仅应用于地球般的 3D 粒子可视化。
- 不改动整页布局、文案或无关的运动系统。
- 用于行星、轨道、基础设施或合成数据地球效果。
- 让核心保持中性或白热,并从设计的主色派生环/辉光的强调色。
视觉目标
- 由发光点构成的密集球形核心。
- 球体周围更薄的外层轨道环或扁平圆盘。
- 清晰的地球轮廓,带倾斜、纵深与分层的粒子密度。
- 暗色氛围背景、克制的辉光、干净的结构与微妙的科幻纵深。
- 高级且电影感,而非俏皮或嘈杂。
HTML 与 CSS
<div class="globe-particles-shell">
<canvas class="globe-particles-canvas" data-globe-particles></canvas>
</div>
.globe-particles-shell {
position: relative;
width: min(100%, 760px);
aspect-ratio: 1 / 1;
}
.globe-particles-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
pointer-events: none;
}
粒子着色器
使用圆形着色器点,使粒子保持清晰发光。
const globeParticleVertex = `
attribute float a_size;
attribute float a_layer;
uniform float u_time;
uniform float u_pointSize;
varying float v_layer;
varying float v_depth;
varying float v_falloff;
void main() {
vec3 pos = position;
float breathe = 1.0 + sin(u_time * 0.65 + a_layer * 4.0) * 0.012;
pos *= breathe;
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_PointSize = u_pointSize * a_size * (1.0 / max(0.18, -mvPosition.z));
gl_Position = projectionMatrix * mvPosition;
v_layer = a_layer;
v_depth = smoothstep(-1.8, 1.8, pos.z);
v_falloff = smoothstep(2.45, 0.25, length(pos));
}
`;
const globeParticleFragment = `
precision highp float;
uniform vec3 u_coreColor;
uniform vec3 u_accentColor;
varying float v_layer;
varying float v_depth;
varying float v_falloff;
void main() {
vec2 uv = gl_PointCoord - 0.5;
float d = length(uv);
float alpha = smoothstep(0.5, 0.0, d);
alpha *= alpha;
vec3 color = mix(u_coreColor, u_accentColor, smoothstep(0.35, 1.0, v_layer));
color += vec3(1.0) * v_depth * 0.08;
color = mix(color * 0.42, color, clamp(v_falloff + v_layer * 0.28, 0.0, 1.0));
alpha *= mix(0.52, 1.0, clamp(v_falloff + v_layer * 0.24, 0.0, 1.0));
gl_FragColor = vec4(color, alpha);
}
`;
Three.js 配方
import * as THREE from "three";
function hexToRgb01(hex) {
const clean = hex.replace("#", "").trim();
const value = clean.length === 3
? clean.split("").map((char) => char + char).join("")
: clean;
return new THREE.Color(
parseInt(value.slice(0, 2), 16) / 255,
parseInt(value.slice(2, 4), 16) / 255,
parseInt(value.slice(4, 6), 16) / 255
);
}
function buildGlobeParticleGeometry(options = {}) {
const sphereCount = options.sphereCount || 2600;
const ringCount = options.ringCount || ;
radius = options. || ;
ringRadius = options. || ;
ringThickness = options. || ;
total = sphereCount + ringCount;
positions = (total * );
sizes = (total);
layers = (total);
( i = ; i < sphereCount; i++) {
z = .() * - ;
theta = .() * . * ;
r = radius * ( + .(.(), ) * );
root = .( - z * z);
index = i * ;
positions[index] = .(theta) * root * r;
positions[index + ] = .(theta) * root * r;
positions[index + ] = z * r;
sizes[i] = + .() * ;
layers[i] = .() * ;
}
( i = ; i < ringCount; i++) {
pointIndex = sphereCount + i;
angle = .() * . * ;
r = ringRadius + (.() - ) * ringThickness;
y = (.() - ) * ringThickness * ;
index = pointIndex * ;
positions[index] = .(angle) * r;
positions[index + ] = y;
positions[index + ] = .(angle) * r;
sizes[pointIndex] = + .() * ;
layers[pointIndex] = + .() * ;
}
geometry = .();
geometry.(, .(positions, ));
geometry.(, .(sizes, ));
geometry.(, .(layers, ));
geometry;
}
() {
(!canvas) {};
renderer = .({
canvas,
: ,
: ,
});
renderer.(, );
renderer.(.(. || , options. || ));
renderer. = .;
scene = .();
camera = .(, , , );
camera..(, , options. || );
accent = options.
? .(options.)
: ((.).().() || );
geometry = (options);
material = .({
: globeParticleVertex,
: globeParticleFragment,
: ,
: ,
: .,
: {
: { : },
: { : options. || },
: { : .(options. || ) },
: { : accent },
},
});
particles = .(geometry, material);
particles.. = options. ?? -;
particles.. = options. ?? ;
scene.(particles);
reduceMotion = .().;
pointer = .(, );
rafId = ;
() {
width = .(, canvas.);
height = .(, canvas.);
renderer.(.(. || , options. || ));
renderer.(width, height, );
camera. = width / height;
camera.();
}
() {
rect = canvas.();
pointer. = ((event. - rect.) / rect. - ) * ;
pointer. = ((event. - rect.) / rect. - ) * ;
}
() {
t = time * ;
material... = t;
mouseStrength = options. ?? ;
breath = reduceMotion ? : .(t * ) * ;
particles.. = t * (options. || );
particles.. = (options. ?? -) + pointer. * mouseStrength;
particles.. = (options. ?? ) + pointer. * mouseStrength;
particles..( + breath);
renderer.(scene, camera);
(!reduceMotion) rafId = (render);
}
() {
(rafId);
();
();
}
();
();
.(, handleResize);
.(, handlePointerMove);
{
(rafId);
.(, handleResize);
.(, handlePointerMove);
geometry.();
material.();
renderer.();
};
}
cleanupGlobe = (.(), {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
});
调节旋钮
- 密度:分别调节
sphereCount 和 ringCount。
- 尺度:调节
radius、ringRadius、ringThickness 和 cameraDistance。
- 颜色:让
coreColor 保持中性;从品牌主色派生 accentColor。
- 运动:调节
rotationSpeed、tiltX、tiltZ、mouseStrength 与呼吸幅度。
- 辉光:调节
pointSize、加法混合与粒子数量,使形状保持清晰。
- 性能:在改动视觉结构之前,先降低粒子数量或限制
maxDpr。
审美准则
- 轮廓必须读作一个地球,而非松散的星空。
- 环应显得有轨道感且倾斜,而非像一条扁平的装饰下划线。
- 使用克制的辉光;让密度与纵深营造高级感。
- 让鼠标响应温和,使物体漂移而非摆动。
- 把地球置于暗色背景之上或暗色氛围外壳之内。
避免
- 没有球形结构的普通星空噪点。
- 破坏地球轮廓的过大粒子或泛光。
- 当设计有明确主色时硬编码强调色。
- 狂野的光标交互或快速旋转。
- 把物体变成模糊团块的浓雾。
快速检查
- 球体与环是两组不同的粒子群。
- 核心读作大体中性或白热。
- 强调色出现在环、高光或辉光上。
- 倾斜揭示出环与地球的纵深。
- 减弱动效时渲染出静止或近乎静止的物体。
- 几何体、材质、渲染器、监听器与 RAF 都被清理。