使用3D图形库three.js构建类似夸克浏览器官网

619 阅读1分钟

Three.js

Three.js是一款运行在浏览器中的 3D 引擎(基于WebGL的API的封装),你可以用它来创造你所需要的一系列3D动画场景。

基本思路

  1. 创建基本的元素
  2. 创建透明二维矩形并贴图模拟手机
  3. 创建透明二维矩形并贴图显示前景
  4. 遍历随机创建其他装饰小物体
  5. 通过监听鼠标移动判断移入事件
  6. 通过移入事件和位置设置手机偏移
  7. tween.js实现物体偏移过渡
  8. 渲染时加入装饰小物体动画
  9. 定时改变装饰小物体移动方向

获取源码

https://github.com/jiangzetian/CanvasDemo/tree/master/filp-card

项目结构

filp-card//项目根目录
|
├── index.html (项目入口)
|
├── css
│   └── index.css//样式文件
├── img
│   ├── pic1.png//图片
|   └── pic2.png//图片
└── js
    └── threejs
        ├── OrbitControls.js//控制库
        ├── three.js//three库
        ├── three.min.js//three库压缩
        ├── tweenjs.js//动画库
        └── tweenjs.min.js//动画库压缩

仿夸克2 .gif

基本代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>翻转卡片</title>
		<link href="css/index.css" rel="stylesheet" />
		<script src="js/threejs/three.js"></script>
		<script src="js/threejs/tweenjs.js"></script>
		<script src="js/threejs/OrbitControls.js"></script>
	</head>
	<body>
		<div id="retina"></div>
	</body>
	<script>
		//页面加载完成
		window.onload = function() {
			create();
		}

		function create() {
			initRender(); //创建渲染器
			initCamera(); //创建相机
			initLight(); //创建光源
			initObject(); //创建物体
			initBgCubes();//创建装饰物体
			initSprite(); //创建图片
			initScene(); //创建场景
			//initControls()//视角控制器 开发时观察用
			initMouse() //鼠标监听
			initBgCubesAnime();//物体移动控制
			render(); //渲染
		}

		//渲染器
		var renderer;
		var width;
		var height;
		function initRender() {}

		//相机
		var camera;
		var origPoint = new THREE.Vector3(0, 0, 0);
		function initCamera() {}

		//光源
		var pointLight;
		var ambientLight;
		function initLight() {}

		//物体1
		var cube;
		function initObject() {}
		
		//装饰物体
		var bgCubes = [];
		var count = 20;
		function initBgCubes() {}
		
		//图片1
		var image_1;
		function initSprite() {}


		//创建场景
		var scene;
		function initScene() {}

		//交互插件
		var controls;
		function initControls() {}
		
		//装饰物体移动
		var cubeV = {v:-0.1}
		function initBgCubesAnime(){}
		
		//渲染
		function render() {}

		//鼠标监听
		function initMouse() {}
	</script>
</html>

完整代码

<!--路径:/index.html-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>翻转卡片</title>
		<link href="css/index.css" rel="stylesheet" />
		<script src="js/threejs/three.js"></script>
		<script src="js/threejs/tweenjs.js"></script>
		<script src="js/threejs/OrbitControls.js"></script>
	</head>
	<body>
		<div id="retina"></div>
	</body>
	<script>
		//页面加载完成
		window.onload = function() {
			create();
		}

		function create() {
			initRender(); //创建渲染器
			initCamera(); //创建相机
			initLight(); //创建光源
			initObject(); //创建物体
			initBgCubes();//创建装饰物体
			initSprite(); //创建图片
			initScene(); //创建场景
			// initControls()//视角控制器
			initMouse() //鼠标监听
			initBgCubesAnime();//物体移动控制
			render(); //渲染
		}

		//渲染器
		var renderer;
		var width;
		var height;

		function initRender() {
			width = window.innerWidth;
			height = window.innerHeight;
			renderer = new THREE.WebGLRenderer({
				antialias: true, //抗锯齿开启
			})
			renderer.setSize(width, height); //设置渲染器宽高
			renderer.setClearColor('#000000', 1.0); //设置背景颜色
			renderer.setPixelRatio(window.devicePixelRatio); //设置设备像素比
			document.getElementById('retina').appendChild(renderer.domElement); //把渲染器放入页面中
		}

		//相机
		var camera;
		var origPoint = new THREE.Vector3(0, 0, 0);

		function initCamera() {
			camera = new THREE.PerspectiveCamera(45, width / height, 1, 2000);
			camera.position.set(0, 0, 900); //相机位置
			camera.up.set(0, 1, 0) //设置相机正方向
			camera.lookAt(origPoint); //设置相机视点
		}

		//光源
		var pointLight;
		var directionalLight ;
		var ambientLight;

		function initLight() {
			//点光源
			pointLight = new THREE.PointLight(0xffffff);
			pointLight.position.set(-800, 0, 1000);
			
			directionalLight  = new THREE.DirectionalLight(0xffffff);
			directionalLight .position.set(-1000, 0, 0);
			
			//环境光
			ambientLight = new THREE.AmbientLight(0xffffff);
			ambientLight.position.set(0, 0, 2000);
		}

		//物体1
		var cube;

		function initObject() {
			var spriteMap = new THREE.TextureLoader().load("img/pic1.png");
			var geometry = new THREE.PlaneBufferGeometry(260,440);
			var material = new THREE.MeshMatcapMaterial({
				map: spriteMap,
				transparent: true,
			});
			cube = new THREE.Mesh(geometry, material);
			cube.position.set(0, 0, 0);
			cube.name = 'cube';
		}
		
		var bgCubes = [];
		var count = 20;
		function initBgCubes() {
			for(let i=0;i<count;i++){
				let geometry;
				let rand = Math.round(Math.random()*(8-1)+1);
				switch(rand){
					case 1:
						geometry = new THREE.BoxGeometry(30, 30, 30);
						break;
					case 2:
						geometry = new THREE.TorusBufferGeometry(20, 8, 16, 100);
						break;
					case 3:
						geometry = new THREE.ConeGeometry(15, 40, 32);
						break;
					case 4:
						geometry = new THREE.CylinderGeometry(10, 10, 30, 32);
						break;
					case 5:
						geometry =  new THREE.TetrahedronBufferGeometry(20);	
						break;
					case 6:
						geometry = new THREE.SphereGeometry( 15, 32, 32 );
						break;	
					case 7:
						geometry = new THREE.OctahedronGeometry( 15 );
						break;	
					case 8:
						geometry = new THREE.DodecahedronGeometry( 15 );
						break;			
				}
				let material = new THREE.MeshPhongMaterial({
					color: 0x3546FF,
				});
				let cube = new THREE.Mesh(geometry, material);
				if(i<count/2){
					let cw = width;
					let ch = height-150;
					let r = 500;
					let maxcount = count/2;
					let x = cw/2-(cw/(maxcount+1))*(i+1);
					let y = ch/2-(ch/(maxcount+1))*(i+1);
					let z = Math.pow(r,2)-Math.pow(cw/2-(cw/(maxcount+1))*(i+1),2);
					cube.position.set(x, y-50, Math.sqrt(z));
				}else{
					let cw = width;
					let ch = height-150;
					let r = 500;
					let maxcount = count/2;
					let index = i - count/2;
					let x = cw/2-(cw/(maxcount+1))*(index+1);
					let y = ch/2-(ch/(maxcount+1))*(index+1);
					let z = Math.pow(r,2)-Math.pow(cw/2-(cw/(maxcount+1))*(index+1),2);
					cube.position.set(x, y, -Math.sqrt(z));
				}	
				bgCubes.push(cube)
			}
		}

		//图片1
		var image_1;

		function initSprite() {
			var spriteMap = new THREE.TextureLoader().load("img/pic2.png");
			var geometry = new THREE.PlaneBufferGeometry(220, 400);
			var material = new THREE.MeshBasicMaterial({
				map: spriteMap,
				transparent: true,
			});
			image_1 = new THREE.Mesh(geometry, material);
			image_1.position.set(0, 0, 100);
		}


		//创建场景
		var scene;

		function initScene() {
			scene = new THREE.Scene();
			scene.add(pointLight);
			scene.add(directionalLight);
			scene.add(ambientLight);
			scene.add(cube);
			for(let i=0;i<bgCubes.length;i++){
				scene.add(bgCubes[i]);
			}
			scene.add(image_1);
		}

		//交互插件
		var controls;

		function initControls() {
			controls = new THREE.OrbitControls(camera, renderer.domElement);
		}
		
		//装饰物体移动
		var cubeV = {v:-0.1}
		function initBgCubesAnime(){
			setInterval(()=>{
				if(cubeV.v>0){
					createjs.Tween.get(cubeV).to({v:-0.1}, 1000).addEventListener('change', e => {
						console.log(cubeV)
					}, false);
				}else{
					createjs.Tween.get(cubeV).to({v:0.1}, 1000).addEventListener('change', e => {
						console.log(cubeV)
					}, false);
				}
			},6000)
		}

		//渲染
		function render() {
			renderer.clear();
			for(let i=0;i<bgCubes.length;i++){
				bgCubes[i].rotation.x += Math.round(Math.random()*(5-1)+1)/1000;
				bgCubes[i].rotation.y += Math.round(Math.random()*(5-1)+1)/1000;
				bgCubes[i].rotation.z += Math.round(Math.random()*(5-1)+1)/1000;
				
				bgCubes[i].position.x += cubeV.v*Math.round(Math.random()*(2-1)+1);
				bgCubes[i].position.y += cubeV.v*Math.round(Math.random()*(2-1)+1);
				bgCubes[i].position.z += cubeV.v*Math.round(Math.random()*(2-1)+1);
			}
			renderer.render(scene, camera);
			requestAnimationFrame(render);
		}

		//鼠标监听
		function initMouse() {
			var mouse = new THREE.Vector2();
			var raycaster = new THREE.Raycaster();
			//移动角度数据
			var rotationData = {
				x: 0,
				y: 0
			};
			//移动距离数据(图片)
			var positionData = {
				x: 0,
				y: 0
			};
			//鼠标移动监听
			document.onmousemove = function(e) {
				var mx = e.clientX;
				var my = e.clientY;
				mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
				mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
				raycaster.setFromCamera(mouse, camera);
				var intersects = raycaster.intersectObjects(scene.children);
				if (intersects.length > 0) {
					for (let i = 0; i < intersects.length; i++) {
						if (intersects[i].object.name == 'cube') {
							createjs.Tween.get(rotationData).to({
								x: Math.PI / 12 * (my - height / 2) / 250,
								y: Math.PI / 12 * (mx - width / 2) / 150
							}, 300).addEventListener('change', e => {
								cube.rotation.set(rotationData.x, rotationData.y, 0);
								image_1.rotation.set(rotationData.x, rotationData.y, 0);
							}, false);

							createjs.Tween.get(positionData).to({
								x: 25 * (mx - width / 2) / 150,
								y: -25 * (my - height / 2) / 250
							}, 300).addEventListener('change', e => {
								image_1.position.set(positionData.x, positionData.y, 100);
							}, false);
						}
					}
				} else {
					createjs.Tween.get(rotationData).to({
						x: 0,
						y: 0
					}, 300).addEventListener('change', e => {
						cube.rotation.set(rotationData.x, rotationData.y, 0);
						image_1.rotation.set(rotationData.x, rotationData.y, 0);
					}, false);

					createjs.Tween.get(positionData).to({
						x: 0,
						y: 0
					}, 300).addEventListener('change', e => {
						image_1.position.set(positionData.x, positionData.y, 100);
					}, false);
				}
			};
		}
	</script>
</html>

更多前端内容欢迎关注公众号:天小天个人网