“使用 Three.js 的 WebGL 小实验。平滑运动的秒针。”
HTML:
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.170.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.170.0/examples/jsm/"
}
}
</script>
body {
overflow: hidden;
margin:0;
}
JAVASCRIPT:
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
console.clear();
class Dial extends THREE.LineSegments {
constructor() {
let radii = [6.0, 5.5];
let axis = new THREE.Vector3(0, 0, 1);
let positions = [...Array(60)]
.map((_, idx, arr) => {
let step = (Math.PI * 2) / arr.length;
return [...radii].map((radius, rIdx) => {
return new THREE.Vector3(0, 1, 0)
.setLength(radius - (idx % 5 == 0 && rIdx == 1 ? 0.25 : 0))
.applyAxisAngle(axis, idx * step);
});
})
.flat();
super(
new THREE.BufferGeometry().setFromPoints(positions),
new THREE.LineBasicMaterial({ color: 0x00aaff })
);
}
}
class SecondsHand extends THREE.Mesh {
constructor() {
let shape = new THREE.Shape()
.moveTo(5, 0)
.lineTo(4.9, 0.05)
.absarc(0, 0, 0.25, Math.PI * 0.1, Math.PI * 1.9, false)
.lineTo(4.9, -0.05);
shape.holes.push(new THREE.Path().absarc(0, 0, 0.1, 0, Math.PI * 2, true));
super(
new THREE.ShapeGeometry(shape).rotateZ(Math.PI * 0.5),
new THREE.MeshBasicMaterial({ color: "lightblue" })
);
this.angleStep = Math.PI * 2 / 60;
this.Easings = {
Back: {
Out: function(amount) { // https://github.com/tweenjs/tween.js/blob/eb07dd2012862269e652129230a71baa4d3608ae/src/Easing.ts#L189
const s = 1.70158;
return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1;
}
},
Bounce: {
Out: function( amount ){ // https://github.com/tweenjs/tween.js/blob/eb07dd2012862269e652129230a71baa4d3608ae/src/Easing.ts#L206
if (amount < 1 / 2.75) {
return 7.5625 * amount * amount
} else if (amount < 2 / 2.75) {
return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75
} else if (amount < 2.5 / 2.75) {
return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375
} else {
return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375
}
}
},
Elastic: {
Out: function (amount) { // https://github.com/tweenjs/tween.js/blob/eb07dd2012862269e652129230a71baa4d3608ae/src/Easing.ts#L155
if (amount === 0) { return 0; }
if (amount === 1) { return 1; }
return ( Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1 );
}
}
};
}
update(){
let time = new Date();
let seconds = time.getSeconds();
let milliseconds = (time.getMilliseconds() / 1000) ** 16;
this.rotation.z = -(seconds + this.Easings.Elastic.Out(milliseconds)) * this.angleStep;
}
}
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 1).setLength(15);
let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", (event) => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
let dial = new Dial();
scene.add(dial);
let secondsHand = new SecondsHand();
scene.add(secondsHand);
renderer.setAnimationLoop(() => {
controls.update();
secondsHand.update();
renderer.render(scene, camera);
});
源码:
https://codepen.io/prisoner849/pen/XWvORjj
体验:
https://codepen.io/prisoner849/full/XWvORjj