发布:完整项目代码(三视图 + 词条数据 + 加载页等)

This commit is contained in:
2026-09-19 16:53:18 +08:00
parent e3fd805de2
commit 906e53f1d5
16 changed files with 1758 additions and 464 deletions
+126
View File
@@ -0,0 +1,126 @@
import * as THREE from 'three';
// OS1 加载环(参考 Her - OS1 Loading):白色扭曲环在陶土橙红背景上旋转,
// 按下加速转向正面、淡出并显露完成圆环。
const BG = '#14100d';
class OS1Curve extends THREE.Curve {
constructor(length, radius) {
super();
this.length = length;
this.radius = radius;
this.pi2 = Math.PI * 2;
}
getPoint(percent) {
const { length, radius, pi2 } = this;
let t = (percent % 0.25) / 0.25;
t = (percent % 0.25) - (2 * (1 - t) * t * -0.0185 + t * t * 0.25);
if (Math.floor(percent / 0.25) === 0 || Math.floor(percent / 0.25) === 2) t *= -1;
const x = length * Math.sin(pi2 * percent);
const y = radius * Math.cos(pi2 * 3 * percent);
const z = radius * Math.sin(pi2 * 2 * (percent - t));
return new THREE.Vector3(x, y, z);
}
}
export function initLoading() {
const wrap = document.getElementById('loading-canvas');
if (!wrap) return { dispose() {} };
const canvassize = 500;
const length = 30;
const radius = 5.6;
const rotatevalue = 0.035;
let acceleration = 0;
let animatestep = 0;
let toend = false;
let disposed = false;
let rafId = 0;
const group = new THREE.Group();
const scene = new THREE.Scene();
scene.add(group);
const camera = new THREE.PerspectiveCamera(65, 1, 1, 10000);
camera.position.z = 150;
// 白色扭曲环
const mesh = new THREE.Mesh(
new THREE.TubeGeometry(new OS1Curve(length, radius), 120, 1.1, 2, true),
new THREE.MeshBasicMaterial({ color: 0xe0c46a, transparent: true }),
);
group.add(mesh);
// 缺口遮挡(背景色)
const ringcover = new THREE.Mesh(
new THREE.PlaneGeometry(50, 15, 1),
new THREE.MeshBasicMaterial({ color: 0x14100d, transparent: true, opacity: 0 }),
);
ringcover.position.x = length + 1;
ringcover.rotation.y = Math.PI / 2;
group.add(ringcover);
// 完成圆环
const ring = new THREE.Mesh(
new THREE.RingGeometry(4.3, 5.55, 32),
new THREE.MeshBasicMaterial({ color: 0xe0c46a, transparent: true, opacity: 0 }),
);
ring.position.x = length + 1.1;
ring.rotation.y = Math.PI / 2;
group.add(ring);
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.setSize(canvassize, canvassize);
renderer.setClearColor(BG);
wrap.appendChild(renderer.domElement);
function easing(t, b, c, d) {
if ((t /= d / 2) < 1) return (c / 2) * t * t + b;
return (c / 2) * ((t -= 2) * t * t + 2) + b;
}
function render() {
animatestep = Math.max(0, Math.min(240, toend ? animatestep + 1 : animatestep - 4));
acceleration = easing(animatestep, 0, 1, 240);
if (acceleration > 0.35) {
let progress = (acceleration - 0.35) / 0.65;
group.rotation.y = (-Math.PI / 2) * progress;
group.position.z = 50 * progress;
progress = Math.max(0, (acceleration - 0.97) / 0.03);
mesh.material.opacity = 1 - progress;
ringcover.material.opacity = ring.material.opacity = progress;
ring.scale.x = ring.scale.y = 0.9 + 0.1 * progress;
}
renderer.render(scene, camera);
}
function animate() {
mesh.rotation.x += rotatevalue + acceleration;
render();
if (!disposed) rafId = requestAnimationFrame(animate);
}
animate();
const press = () => { toend = true; };
const release = () => { toend = false; };
document.body.addEventListener('mousedown', press);
document.body.addEventListener('touchstart', press);
document.body.addEventListener('mouseup', release);
document.body.addEventListener('touchend', release);
return {
dispose() {
disposed = true;
cancelAnimationFrame(rafId);
document.body.removeEventListener('mousedown', press);
document.body.removeEventListener('touchstart', press);
document.body.removeEventListener('mouseup', release);
document.body.removeEventListener('touchend', release);
renderer.dispose();
if (renderer.domElement.parentNode === wrap) wrap.removeChild(renderer.domElement);
},
};
}