参考学习
https://www.ituring.com.cn/book/miniarticle/53322
播放设置(暂停、时间段、时间点)
https://blog.csdn.net/u014291990/article/details/103350524
放大缩小
https://blog.csdn.net/liu4071325/article/details/52302053
动画原理
动画的本质就是利用人眼的视觉暂留特性,快速变换画面,从而产生物体运动的假象。Three.js则是通过每秒中多次重绘实现动画的。
FPS是(Frames Per Second)是指每秒画面重绘的次数。FPS越大,则动画效果越平滑,FPS小于20会有画面卡滞现象。当FPS达到60,再增加帧数人眼也不会明显感觉到变化,电影的FPS标准为24,Three.js动画,一般FPS在30-60
setInterval方法
可自定义fps
setInterval(func, msec)
func是每过msec毫秒执行的函数,如果将func定义为重绘画面的函数,就能实现动画效果。setInterval函数返回一个id,如果需要停止重绘,需要使用clearInterval方法,并传入该id
需要在init函数中调用
id = setInterval(draw, 20);
//每帧中的变化(毕竟,如果每帧都是相同的,即使重绘再多次,还是不会有动画的效果),这里我们让场景中的长方体绕y轴转动
function draw() {
mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
}
每20毫秒就会调用一次draw函数,改变长方体的旋转值,然后进行重绘。最终得到的效果就是FPS为50的旋转长方体。
我们在HTML中添加一个按钮,按下后停止动画:
<button id="stopBtn" onclick="stop()">Stop</button>
function stop() {
if (id !== null) {
clearInterval(id);
id = null;
}
}
例子1
1.插件平移旋转
2.点击变色
3.自动旋转
4.点击停止按钮停止旋转
按钮调用draw函数时:
点击开始按钮,每点击一次,变化一次,旋转增加0.01
按钮调用start函数时:
动画核心代码
<body>
<script src="js/three.js"></script>
<script>
<button id="stopBtn" onclick="stop()">stop</button>
<button id="startBtn" onclick="start()">start</button>
function init() {
// init scene
//省略
// init camera
//省略
//light
//省略
//加载两个由C4D用blender转成fbx再转成json的模型文件
//用mesh1 = obj1,mesh2=obj2,可以控制多个模型的动画
var loaderC1 = new THREE.ObjectLoader();
loaderC1.load("json/cfbx3.json", function(obj1) {
obj1.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide;
}
});
obj1.scale.multiplyScalar(3);//3倍大小
mesh1 = obj1;
obj1.position.set(0,1.3,8.5)
obj1.rotation.z = -Math.PI;//旋转180度
scene.add(obj1);
});
var loaderC2 = new THREE.ObjectLoader();
loaderC2.load("json/cfbx3.json", function(obj2) {
obj2.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide;
}
});
obj2.scale.multiplyScalar(3);//3倍大小
mesh2 = obj2;
obj2.position.set(0,1.3,6)
obj2.rotation.z = -Math.PI;//旋转180度
scene.add(obj2);
});
//动画重绘 每20毫秒调用一次draw函数,我理解就是20毫秒重绘一次
id = setInterval(draw, 20);
}//init end
//动画
function draw() {
//两个模型旋转
mesh1.rotation.y = (mesh1.rotation.y + 0.01) % (Math.PI * 2);
mesh2.rotation.y = (mesh1.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
}
//停止按钮
function stop() {
if (id !== null) {
clearInterval(id);
id = null;
}
}
//重启按钮
function start() {
mesh1.rotation.y = (mesh1.rotation.y + 0.01) % (Math.PI * 2);
mesh2.rotation.y = (mesh1.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
id = setInterval(draw, 20);
}
</script>
</body>
完整代码
rotationDraw和otherDraw函数每次按动按钮只执行一次,要想多次执行需要再次调用setInterval(otherDraw, 20),让它每毫秒重绘20次,因此把它放在start函数里就可以实现暂停后再播放的效果
<!DOCTYPE html>
<html lang="en">
<head>
<title>3D</title>
<meta charset="utf-8">
<!-- 自适应 -->
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<button class="startBtn" onclick="start1()">start1</button>
<button class="startBtn" onclick="otherDraw()">start2</button>
<script src="js/three.js"></script>
<script type="text/javascript" src="js/OrbitControls.js"></script>
<script>
var stats, light, mesh ,group;
var camera, scene, raycaster, renderer;
var mouse = new THREE.Vector2(), INTERSECTED;//相交的
var radius = 100, theta = 0;
init();
function init() {
// init scene
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
// init camera
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(15,15,15);
// camera.lookAt(new THREE.Vector3(0,0,0));
camera.lookAt( scene.position );
scene.add(camera);
//light
var light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 1, 1, 1 ).normalize();//向量属性向量转换为单位向量,方向设置为和原向量相同,长度为1
light.intensity=1.5;//强度
scene.add( light );
//缓存库
var loaderC1 = new THREE.ObjectLoader();
loaderC1.load("json/cfbx3.json", function(obj1) {
obj1.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide;
}
});
obj1.scale.multiplyScalar(3);//3倍大小
mesh1 = obj1;
obj1.position.set(0,1.3,8.5)
obj1.rotation.z = -Math.PI;//旋转180度
scene.add(obj1);
});
var loaderC2 = new THREE.ObjectLoader();
loaderC2.load("json/cfbx3.json", function(obj2) {
obj2.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.material.side = THREE.DoubleSide;
}
});
obj2.scale.multiplyScalar(3);//3倍大小
mesh2 = obj2;
obj2.position.set(0,1.3,6)
obj2.rotation.z = -Math.PI;//旋转180度
scene.add(obj2);
});
//射线
raycaster = new THREE.Raycaster();
/* var raycaster = new THREE.Raycaster();
var mouseVector = new THREE.Vector3();*/
//renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementsByTagName("body")[0].appendChild(renderer.domElement);
//监听
document.addEventListener( "mousedown", onDocumentMouseDown, false );
//窗口变化
window.addEventListener( "resize", onWindowResize, false );
//插件
var controls = new THREE.OrbitControls( camera, renderer.domElement );//camera和render的变量和照相机与渲染器设置的变量一致才行
// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
//设置相机距离原点的最远距离
controls.minDistance = 1;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
//动画重绘
id = setInterval(rotationDraw, 20);
}//function结束
//窗口变化
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
// camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
//坐标转换
function onDocumentMouseDown( event ) {//鼠标事件开始
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// 通过摄像机和鼠标位置更新射线
raycaster.setFromCamera( mouse, camera );
// 计算物体和射线的焦点
var intersects = raycaster.intersectObjects( scene.children ,true);//射线穿过物体,自动由近到远排序
//第二种
if ( intersects.length > 0 ) {//有物体的时候
if ( INTERSECTED != intersects[ 0 ].object ) {//上一次选中不等于当前的选中,就是替换的时候,intersects[ 0 ]就是当前的选中 在最前面的,它是自动排序的
if ( INTERSECTED )
if( INTERSECTED.material .length==undefined){
INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );//上一次选中的要换回原来的材料
}
INTERSECTED = intersects[ 0 ].object;
if( INTERSECTED.material .length==undefined){
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();//把当前的材料保存起来
INTERSECTED.material.emissive.setHex( 0xff0000 );//换颜色
}
}
} else {//选中空白处的时候
//
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
INTERSECTED = null;
}
}//mousedown鼠标事件结束
//动画
function rotationDraw() {
mesh1.rotation.y = (mesh1.rotation.y + 0.01) % (Math.PI * 2);
mesh2.rotation.y = (mesh1.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
}
function otherDraw() {
mesh1.position.y = mesh1.position.y + 1;
mesh2.scale.x = mesh2.scale.x * 2;
mesh2.scale.y = mesh2.scale.y * 2;
mesh2.scale.z = mesh2.scale.z * 2;
// mesh2.scale.multiplyScalar = mesh2.scale.multiplyScalar(2);
renderer.render(scene, camera);
}
function stop() {
if (id !== null) {
clearInterval(id);
id = null;
}
}
function start1() {
id = setInterval(rotationDraw, 20);
}
function start2() {
id = setInterval(otherDraw, 20);
}
</script>
</body>
</html>
requestAnimationFrame方法
这个方法没有写出来效果,不知为啥
init函数中
id = requestAnimationFrame(draw);
取消动画
function stop() {
if (id !== null) {
cancelAnimationFrame(id);
id = null;
}
}
和setInterval不同的是,由于requestAnimationFrame只请求一帧画面,因此,除了在init函数中需要调用,在被其调用的函数中需要再次调用requestAnimationFrame:
function draw() {
mesh.rotation.y = (mesh.rotation.y + 0.01) % (Math.PI * 2);
renderer.render(scene, camera);
id = requestAnimationFrame(draw);
}
为了支持这些浏览器,我们最好在调用之前,先判断是否定义了requestAnimationFrame以及上述函数:
var requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;