127 lines
4.0 KiB
JavaScript
127 lines
4.0 KiB
JavaScript
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);
|
|
},
|
|
};
|
|
}
|