Threejs实现加载loading动画

发布于:2025-03-07 ⋅ 阅读:(41) ⋅ 点赞:(0)

大家好!我是 [数擎AI],一位热爱探索新技术的前端开发者,在这里分享前端和Web3D、AI技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步!
开发领域:前端开发 | AI 应用 | Web3D | 元宇宙
技术栈:JavaScript、React、ThreeJs、WebGL、Go
经验经验:6 年+ 前端开发经验,专注于图形渲染和 AI 技术
开源项目智简未来晓智科技数擎科技

核心实现原理

1. 坐标系统转换 ‌

  • 将屏幕空间坐标转换为归一化极坐标系:
vec2 uv = vUv * vec2(iResolution.x/iResolution.y, 1.0);
uv -= vec2(0.87, 0.5);

通过保持宽高比一致性 ‌57,确保图形在不同分辨率下保持正确比例

2. 旋转动画矩阵 ‌

  • 基于时间变量生成动态旋转角度:
float angle = -(time - sin(time + PI) * cos(time)) - time * 0.95;
mat2 rot = mat2(cos(angle), sin(angle), -sin(angle), cos(angle));

这种非线性时间函数 ‌36 创造了复杂的运动轨迹

3. 形状绘制
float L = length(p);
float f = 0.;
f = smoothstep(L - .005, L, .35);
f -= smoothstep(L, L + 0.005, .27);
  • L < 0.35 形成外圈。
  • L > 0.27 内部减弱,形成环形带。
4. 角度控制弧形
float t = mod(time, TPI) - PI;
float t1 = -PI;
float t2 = sin(t) * (PI - .25);
  • t:随时间变化的角度范围(mod(time, TPI) - PI)。
  • t1, t2:限制弧形的角度范围。
float a = atan(p.y, p.x);
f = f * step(a, t2) * (1. - step(a, t1));
  • a = atan(p.y, p.x) 计算当前像素的极坐标角度。
  • step(a, t2) * (1. - step(a, t1)) 限制 f 仅在 [t1, t2] 之间,使其成为弧形,而不是完整圆环。
5. 颜色计算
col = mix(col, vec3(cos(time), cos(time + TPI / 3.), cos(time + 2. * TPI / 3.)), f);
  • vec3(cos(time), cos(time + TPI / 3.), cos(time + 2. * TPI / 3.)) 生成基于 cos(time) 变化的 RGB 颜色,实现动态色彩过渡。
  • mix(col, color, f) 按 f 进行颜色混合,使得弧形具有渐变色。
  1. 输出颜色
gl_FragColor = vec4(col, 1.0);

  • 将计算出的颜色 col 赋值给 gl_FragColor,用于渲染像素。

完整代码

import * as THREE from 'three';

// 创建场景
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
camera.position.z = 1;

// 创建 WebGL 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 片段着色器
const fragmentShader = `
#define PI 3.14159265359
#define TPI 6.28318530718
#define HPI 1.57079632679

uniform vec2 iResolution;
uniform float iTime;

void main() {
    vec2 fragCoord = gl_FragCoord.xy;
    vec2 uv = fragCoord.xy / iResolution.y;

    vec2 p = uv - vec2(.87,.5);
    float time = iTime * 1.5;
    
    float angle = -(time - sin(time + PI) * cos(time )) - time * .95;
    mat2 rot = mat2(cos(angle), sin(angle), -sin(angle), cos(angle));
    p = rot * p;
    
    vec3 col = vec3(0.);
    float L = length(p);
    float f = 0.;
    
    f = smoothstep(L - .005, L, .35);
    f -= smoothstep(L, L + 0.005, .27);
    
    float t = mod(time, TPI) - PI;
    float t1 = -PI;
    float t2 = sin(t) * (PI - .25);
    
    float a = atan(p.y, p.x);
    f = f * step(a, t2) * (1. - step(a, t1));
    
    col = mix(col, vec3(cos(time), cos(time + TPI / 3.), cos(time + 2. * TPI / 3.)), f);

    gl_FragColor = vec4(col, 1.0);
}
`;

// 顶点着色器
const vertexShader = `
void main() {
    gl_Position = vec4(position, 1.0);
}
`;

// 创建 ShaderMaterial
const material = new THREE.ShaderMaterial({
  uniforms: {
    iResolution: {
      value: new THREE.Vector2(window.innerWidth, window.innerHeight),
    },
    iTime: { value: 0 },
  },
  vertexShader,
  fragmentShader,
});

// 创建屏幕四边形
const geometry = new THREE.PlaneGeometry(2, 2);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// 动画循环
function animate() {
  material.uniforms.iTime.value = performance.now() / 1000;
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

animate();

// 窗口大小变化时更新
window.addEventListener('resize', () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  material.uniforms.iResolution.value.set(
    window.innerWidth,
    window.innerHeight,
  );
});

总结

  • 坐标归一化:通过 iResolution.y 让形状不会因窗口比例变形。
  • 旋转矩阵:使用 mat2 进行旋转计算。
  • 平滑边缘:smoothstep() 处理弧形边缘,使其更柔和。
  • 角度控制:atan() 限制显示角度,形成弧形而非完整圆环。
  • 颜色变化:cos(time) 生成动态 RGB 颜色过渡。