在这里插入图片描述

不经意间看到了某个大佬做的网站~ 实在是太帅啦!
查了查实现该效果的技术 —— 原来是Three.js
如果你也感兴趣的话,那就让我们来从零开始学习Three.js动态3D效果吧✨


一、了解

Three.js是基于原生WebGL封装运行的三维引擎,在所有WebGL引擎中,Three.js是国内文资料最多、使用最广泛的三维引擎。

Threejs是一款WebGL三维引擎,它可以用来做许多许多地场景应用

示例

在这里插入图片描述

那么不热爱前端的你,是否会因为这张图片而热爱呢?
没错,这张图片就是用Three.js框架实现的。

二、安装

本教程采用的安装方法是第一种,所以需要大家将Three.js文件下载到本地,添加到自己的项目中。

1.本地安装

创建Three.Js文件夹
创建index.html文件
创建js文件夹 将下载的three.js包放入js文件夹中


在这里插入图片描述

百度网盘链接:

链接: https://pan.baidu.com/s/1pSA-txD1hA7SknK0_htTvg?pwd=cad6
提取码:cad6

网盘里有Three.js文件,大家可以直接使用,如果大家想使用Three.js框架的其他模块,可自行去GitHub上下载,地址在下面:

官方GitHub地址: https://github.com/mrdoob/three.js/tree/master

2.使用 NPM 和构建工具进行安装

注:这种方法对新手十分不友好,新手建议使用第一种方法

由于项目需要的依赖越多,就越有可能遇到静态托管无法轻易解决的问题。而使用构建工具,导入本地 JavaScript 文件和 npm 软件包将会解决问题,无需导入映射(import maps)。

  1. 安装 Node.js。我们需要它来管理依赖项和运行构建工具。
  2. 在项目文件夹中通过 终端 安装 three.js 和构建工具 Vite。Vite将在开发过程中使用,但不会被打包成为最终网页的一部分。当然,除了 Vite 你也可以使用其他支持导入 ES Modules的现代构建工具。
# three.js
npm install --save three

# vite
npm install --save-dev vite
  1. 在终端运行:
npx vite

4.如果一切顺利,你会在终端中看到一个类似 http://localhost:5173 的 URL,打开该 URL 就能看到你的网络应用程序。

之后,当你准备部署网络应用程序时,只需要让 Vite 运行生产构建 - npx vite build。应用程序使用的所有内容都将被编译、优化并复制到 dist/ 文件夹中。该文件夹中的内容就可以托管到你的网站上了。

3.从CDN导入

  • 在 index.html 中,我们需要添加一个导入映射(import map)来定义从哪里获取软件包。将下面的代码放在
    <head></head> 标签内、样式(styles)之后。
<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/"
    }
  }
</script>
  • 不要忘记将上述链接中的 替换为 three.js 的实际版本,如 “v0.149.0”。最新版本可在 npm
    版本列表中找到。

三、基础教程

这一部分将对 three.js 来做一个简要的介绍;我们将开始搭建一个场景,其中包含一个正在旋转的立方体。

创建一个场景

为了真正能够让你的场景借助 three.js 来进行显示,我们需要以下几个对象:场景(scene)、相机(camera)和渲染器(renderer),这样我们就能透过摄像机渲染出场景。

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

上面是一段js代码,在上面的代码中,我们建立了场景、相机和渲染器。

three.js 里有几种不同的相机,在这里,我们使用的是 PerspectiveCamera(透视摄像机)。

第一个参数是视野角度(FOV)。视野角度就是无论在什么时候,你所能在显示器上看到的场景的范围,它的单位是角度(与弧度区分开)。

第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。比如说,当你在一个宽屏电视上播放老电影时,可以看到图像仿佛是被压扁的。

接下来的两个参数是近截面(near)和远截面(far)。 当物体某些部分比摄像机的远截面远或者比近截面近的时候,这些部分将不会被渲染到场景中。或许现在你不用担心这个值的影响,但未来为了获得更好的渲染性能,你将可以在你的应用程序里去设置它。

接下来是渲染器。这里是施展魔法的地方。除了我们在这里用到的 WebGLRenderer 渲染器之外,Three.js 同时提供了其他几种渲染器。

除了创建一个渲染器的实例之外,我们还需要在我们的应用程序里设置一个渲染器的尺寸。比如说,我们可以使用所需要的渲染区域的宽高,来让渲染器渲染出的场景填充满我们的应用程序。因此,我们可以将渲染器宽高设置为浏览器窗口宽高。

如果你希望保持你的应用程序的尺寸,但是以较低的分辨率来渲染,你可以在调用 setSize 时,将 updateStyle(第三个参数)设为 false。例如,假设你的 <canvas> 标签现在已经具有了 100% 的宽和高,调用 setSize(window.innerWidth/2, window.innerHeight/2, false) 将使得你的应用程序以四分之一的大小来进行渲染。

最后一步很重要,我们将 renderer(渲染器)的dom元素(renderer.domElement)添加到我们的 HTML 文档中。这就是渲染器用来显示场景给我们看的 <canvas> 元素。

“嗯,看起来很不错,那你说的那个立方体在哪儿?”

接下来,我们就来添加立方体。

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

camera.position.z = 5;

要创建一个立方体,我们需要一个 BoxGeometry(立方体)对象. 这个对象包含了一个立方体中所有的顶点(vertices)和面(faces)。

接下来,对于这个立方体,我们需要给它一个材质,来让它有颜色。Three.js 自带了几种材质,在这里我们使用的是 MeshBasicMaterial。所有的材质都存有应用于他们的属性的对象。在这里为了简单起见,我们只设置一个color属性,值为 0x00ff00,也就是绿色。这里所做的事情,和在 CSS 或者 Photoshop 中使用十六进制(hex colors)颜色格式来设置颜色的方式一致。

第三步,我们需要一个 Mesh(网格)。 网格包含一个几何体以及作用在此几何体上的材质,我们可以直接将网格对象放入到我们的场景中,并让它在场景中自由移动。

默认情况下,当我们调用 scene.add() 的时候,物体将会被添加到 (0,0,0) 坐标。但将使得摄像机和立方体彼此在一起。为了防止这种情况的发生,我们只需要将摄像机稍微向外移动一些即可。

渲染场景

现在,如果将之前写好的代码复制到HTML文件中,你不会在页面中看到任何东西。这是因为我们还没有对它进行真正的渲染。为此,我们需要使用一个被叫做“**渲染循环”(render loop)**或者“动画循环”(animate loop)的东西。

function animate() {
	requestAnimationFrame( animate );
	renderer.render( scene, camera );
}
animate();

在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)。

使立方体动起来

在开始之前,如果你已经将上面的代码写入到了你所创建的文件中,你可以看到一个绿色的方块。让我们来做一些更加有趣的事 —— 让它旋转起来。

将下列代码添加到 animate() 函数中 renderer.render 调用的上方:

cube.rotation.x += 0.01;
cube.rotation.y += 0.01;

这段代码每帧都会执行(正常情况下是60次/秒),这就让立方体有了一个看起来很不错的旋转动画。基本上来说,当应用程序运行时,如果你想要移动或者改变任何场景中的东西,都必须要经过这个动画循环。

结果

祝贺你!你现在已经成功完成了你的第一个 three.js 应用程序。

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>My first three.js app</title>
		<script src="js/three.js"></script>
		<style>
			body {
				margin: 0;
			}
		</style>
	</head>
	<body>
		<script>
			const scene = new THREE.Scene();
			const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

			const renderer = new THREE.WebGLRenderer();
			renderer.setSize(window.innerWidth, window.innerHeight);
			document.body.appendChild(renderer.domElement);

			const geometry = new THREE.BoxGeometry(1, 1, 1);
			const material = new THREE.MeshBasicMaterial({
				color: 0x00ff00
			});
			const cube = new THREE.Mesh(geometry, material);
			scene.add(cube);

			camera.position.z = 5;

			function animate() {
				requestAnimationFrame(animate);

				cube.rotation.x += 0.01;
				cube.rotation.y += 0.01;

				renderer.render(scene, camera);
			}

			animate();
		</script>
	</body>
</html>

在这里插入图片描述

四、其他示例

示例1:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script src="js/three.js"></script>
		<style type="text/css">
			body {
				background-color: #222;
			}

			canvas {
				width: 100%;
				height: 100%;
			}
		</style>
	</head>
	<body>
	</body>
	<script type="text/javascript">
		var scene = new THREE.Scene();
		var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
		var renderer = new THREE.WebGLRenderer();

		renderer.setSize(window.innerWidth, window.innerHeight);
		document.body.appendChild(renderer.domElement);

		var spotLight = new THREE.SpotLight(0xffffff);
		spotLight.position.set(100, 100, 100);
		spotLight.castShadow = true; 
		spotLight.shadowMapWidth = 1024;
		spotLight.shadowMapHeight = 1024;
		spotLight.shadowCameraNear = 500;
		spotLight.shadowCameraFar = 4000;
		spotLight.shadowCameraFov = 30;
		scene.add(spotLight);

		function Mat() {
			var material = new THREE.MeshPhongMaterial({
				color: new THREE.Color("rgb(35,35,213)"),
				emissive: new THREE.Color("rgb(64,128,255)"),
				specular: new THREE.Color("rgb(93,195,255)"),

				shininess: 1,
				shading: THREE.FlatShading,
				wireframe: 1,
				transparent: 1,
				opacity: 0.15
			});
			return material;
		}

		var geometry = new THREE.SphereGeometry(50, 20, 20, 0, Math.PI * 2, 0, Math.PI);

		var earth = new THREE.Mesh(geometry, Mat());

		scene.add(earth);

		camera.position.z = 90;

		function render() {
			requestAnimationFrame(render);
			earth.rotation.x += 0.01;
			earth.rotation.y += 0.01;
			renderer.render(scene, camera);
		}
		render();
	</script>
</html>

示例二(JavaScript编写):

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
	</body>
	<script type="text/javascript">
		"use strict"; 
		const body = document.getElementsByTagName("body").item(0);
		body.style.background = "#000";
		const TP = 2 * Math.PI;
		const CSIZE = 400;

		const ctx = (() => {
			let d = document.createElement("div");
			d.style.textAlign = "center";
			body.append(d);
			let c = document.createElement("canvas");
			c.width = c.height = 2 * CSIZE;
			d.append(c);
			return c.getContext("2d");
		})();
		ctx.translate(CSIZE, CSIZE);
		ctx.lineCap = "round";

		onresize = () => {
			let D = Math.min(window.innerWidth, window.innerHeight) - 40;
			ctx.canvas.style.width = ctx.canvas.style.height = D + "px";
		}

		const getRandomInt = (min, max, low) => {
			if (low) return Math.floor(Math.random() * Math.random() * (max - min)) + min;
			else return Math.floor(Math.random() * (max - min)) + min;
		}

		function Color() {
			const CBASE = 144;
			const CT = 255 - CBASE;
			this.getRGB = (ct) => {
				let red = Math.round(CBASE + CT * (Math.cos(this.RK2 + ct / this.RK1)));
				let grn = Math.round(CBASE + CT * (Math.cos(this.GK2 + ct / this.GK1)));
				let blu = Math.round(CBASE + CT * (Math.cos(this.BK2 + ct / this.BK1)));
				return "rgb(" + red + "," + grn + "," + blu + ")";
			}
			this.randomize = () => {
				this.RK1 = 360 + 360 * Math.random();
				this.GK1 = 360 + 360 * Math.random();
				this.BK1 = 360 + 360 * Math.random();
				this.RK2 = TP * Math.random();
				this.GK2 = TP * Math.random();
				this.BK2 = TP * Math.random();
			}
			this.randomize();
		}

		var color = new Color();

		var stopped = true;
		var start = () => {
			if (stopped) {
				stopped = false;
				requestAnimationFrame(animate);
			} else {
				stopped = true;
			}
		}
		body.addEventListener("click", start, false);

		var dur = 2000;
		var tt = getRandomInt(0, 1000);
		var pause = 0;
		var trans = false;
		var animate = (ts) => {
			if (stopped) return;
			tt++;
			draw();
			requestAnimationFrame(animate);
		}

		var KF = Math.random();

		var Circle = function() {
			this.dir = false;
			this.r = 80;
			this.randomize = () => {
				this.ka = 0; //TP*Math.random();
				this.ka2 = 200;
			}
			this.randomize();
			this.setRA = () => {
				this.a2 = TP / 4 + 1.57 * (1 + Math.sin(tt / this.ka2)) / 2;
				if (this.dir) this.a2 = -this.a2;
			}
			this.setPath2 = () => {
				if (this.p) {
					if (this.cont) {
						this.a = TP / 2 + this.p.a - this.p.a2;
						this.x = this.p.x + (this.p.r - this.r) * Math.cos(this.p.a - this.p.a2);
						this.y = this.p.y + (this.p.r - this.r) * Math.sin(this.p.a - this.p.a2);
					} else {
						this.a = this.p.a - this.p.a2;
						this.x = this.p.x + (this.p.r + this.r) * Math.cos(this.p.a - this.p.a2);
						this.y = this.p.y + (this.p.r + this.r) * Math.sin(this.p.a - this.p.a2);
					}
				} else {
					this.x = this.r * Math.cos(this.a);
					this.y = this.r * Math.sin(this.a);
				}
				this.path = new Path2D();
				this.path.arc(this.x, this.y, this.r, TP / 2 + this.a, this.a - this.a2, this.dir);
			}
		}

		onresize();

		var ca = [];
		var reset = () => {
			ca = [new Circle()];
			ca[0].p = false;
			ca[0].a = 0; //TP*Math.random();
			ca[0].setRA();
			ca[0].x = ca[0].r * Math.cos(ca[0].a);
			ca[0].y = ca[0].r * Math.sin(ca[0].a);
		}
		reset();

		var addCircle = (c) => {
			let c2 = new Circle();
			c2.a = c.a - c.a2;
			c2.dir = !c.dir;
			c2.p = c;
			c2.cont = false;
			c2.r = c.r * 0.8;
			if (Math.random() < KF)
				c2.ka2 = c.ka2 * 0.8;
			ca.push(c2);
			let c3 = new Circle();
			c3.a = TP / 2 + c.a - c.a2;
			c3.dir = c.dir;
			c3.p = c;
			c3.cont = true;
			c3.r = c.r * 0.8;
			if (Math.random() < 1 - KF)
				c3.ka2 = c.ka2 * 0.8;
			ca.push(c3);
		}

		const dmx = new DOMMatrix([-1, 0, 0, 1, 0, 0]);
		const dmy = new DOMMatrix([1, 0, 0, -1, 0, 0]);
		const dmxy = new DOMMatrix([-1, 0, 0, -1, 0, 0]);
		//const dmq=new DOMMatrix([0,1,-1,0,0,0]);
		const getXYPath = (spath) => {
			this.level = 1;
			let p = new Path2D(spath);
			p.addPath(p, dmxy);
			return p;
		}

		var draw = () => {
			ca[0].a = tt / 1000;
			for (let i = 0; i < ca.length; i++) ca[i].setRA();
			for (let i = 0; i < ca.length; i++) ca[i].setPath2();
			let pa = new Array(8);
			for (let i = 0; i < pa.length; i++) pa[i] = new Path2D();
			for (let i = 0; i < ca.length; i++) {
				if (i == 0) pa[0].addPath(ca[i].path);
				else if (i < 3) pa[1].addPath(ca[i].path);
				else if (i < 7) pa[2].addPath(ca[i].path);
				else if (i < 15) pa[3].addPath(ca[i].path);
				else if (i < 31) pa[4].addPath(ca[i].path);
				else if (i < 63) pa[5].addPath(ca[i].path);
				else if (i < 127) pa[6].addPath(ca[i].path);
				else pa[7].addPath(ca[i].path);
			}
			ctx.clearRect(-CSIZE, -CSIZE, 2 * CSIZE, 2 * CSIZE);

			for (let i = 0; i < pa.length; i++) {
				let pth = getXYPath(pa[i]);
				ctx.strokeStyle = color.getRGB(tt - 180 * i);
				ctx.lineWidth = Math.max(3, 24 - 3 * i);
				ctx.stroke(pth);
			}
		}

		for (let i = 0; i < 127; i++) addCircle(ca[i]);

		start();
	</script>
</html>

在这里插入图片描述
在这里插入图片描述

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐