当前位置:   article > 正文

Three.js--》穿越虚拟门槛打造的3D登录界面

Three.js--》穿越虚拟门槛打造的3D登录界面

今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。

目录

项目搭建

初始化three代码

添加背景与地球

星星动画效果

星云动画效果

实现登录框效果


项目搭建

本案例还是借助框架书写three项目,借用vite构建工具搭建vue项目,vite这个构建工具如果有不了解的朋友,可以参考我之前对其讲解的文章:vite脚手架的搭建与使用。搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖即可。接下来对项目进行一些初始化操作:

重置默认样式:在项目中我们都会用到一些标签,但是这些标签可能本身自带一些默认样式,这些默认样式可能会影响我们的排版布局,如果每次引用就去清除一遍默认样式有点太过繁琐,因此这里需要我们清除一下默认样式。执行如下命令安装第三方包: 

npm install reset.css --save

配置scss预处理器:SASS是一种预编译的CSS,作用类似于Less,这里我们在vue项目中采用该预处理器进行处理样式,终端执行如下命令安装相应的插件: 

npm install sass

配置element-plus组件库:因为本项目需要采用 element-plus 组件库进行创建项目,其官方地址为:element-plus ,所以接下来需要对组件库进行一个安装配置,具体的实现过程如下,终端执行如下安装命令:  

npm install element-plus @element-plus/icons-vue

安装完成之后,在入口文件main.js对element的插件进行一个挂载,这里顺便配置一下国家化:   

  1. import { createApp } from 'vue'
  2. import 'reset.css'
  3. import App from './App.vue'
  4. import ElementPlus from 'element-plus' // 引入element-plus插件与样式
  5. import 'element-plus/dist/index.css'
  6. import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
  7. createApp(App)
  8. .use(ElementPlus, { locale: zhCn }) // 安装element-plus插件并进行国际化配置
  9. .mount('#app')

路由配置:因为本项目主要展示的是3D场景的登陆页面,所有这里也是需要配置一些路由的,先我们要先创建几个路由作为路由模块,在src目录下新建一个pages文件夹,其用于存放路由组件相关的内容,如下:

不清楚vue路由的使用,推荐看一下我之前讲解过的文章:Vue 3 路由进阶 接下来正式开始配置项目的路由,首先终端执行如下命令安装vue路由:

npm i vue-router

安装完插件之后,接下来就可以对路由进行相关配置了,在src,目录下新建router文件,如下:

  1. import { createRouter, createWebHistory } from "vue-router"
  2. const routes = [
  3. {
  4. path: '/',
  5. redirect: '/index', // 重定向
  6. },
  7. {
  8. path: '/index',
  9. name: 'home',
  10. component: () => import('../pages/home/index.vue'),
  11. meta: {
  12. title: '首页'
  13. }
  14. },
  15. {
  16. path: '/login',
  17. name: 'login',
  18. component: () => import('../pages/login/index.vue'),
  19. meta: {
  20. title: '登录页'
  21. }
  22. }
  23. ]
  24. // createRouter用于创建路由器实例,可以管理多个路由
  25. const router = createRouter({
  26. // 路由的模式的设置
  27. history: createWebHistory(),
  28. routes
  29. })
  30. export default router

配置完路由组件之后,我们需要在入口文件 main.js 中进行引入安装,如下: 

  1. import { createApp } from 'vue'
  2. import 'reset.css'
  3. import App from './App.vue'
  4. import router from './router' // 引入路由组件
  5. import ElementPlus from 'element-plus' // 引入element-plus插件与样式
  6. import 'element-plus/dist/index.css'
  7. import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
  8. createApp(App)
  9. .use(router) // 安装路由插件
  10. .use(ElementPlus, { locale: zhCn }) // 安装element-plus插件并进行国际化配置
  11. .mount('#app')

安装完成之后,我们需要设置一下路由的出口位置,接下来只需要在根组件App.vue设置出口即可

最终呈现的效果如下所示:

初始化three代码

本次项目使用three.js代码必须要基于下面的基础代码才能实现:

初始化场景

  1. import * as THREE from 'three'
  2. const scene = new THREE.Scene()
  3. scene.fog = new THREE.Fog(0x000000, 0, 10000) // 添加雾的效果

初始化相机

  1. const camera = new THREE.PerspectiveCamera(15, window.innerWidth / window.innerHeight, 1, 30000)
  2. // 计算相机距离物体的位置
  3. const distance = window.innerWidth / 2 / Math.tan(Math.PI / 12)
  4. const zAxisNumber = Math.floor(distance - 1400 / 2)
  5. camera.position.set(0, 0, zAxisNumber) // 设置相机所在位置
  6. camera.lookAt(0, 0, 0) // 看向原点
  7. scene.add(camera)

初始化渲染器

  1. const renderer = new THREE.WebGLRenderer({ antialias: true })
  2. renderer.setSize(window.innerWidth, window.innerHeight)

监听屏幕大小的改变,修改渲染器的宽高和相机的比例

  1. window.addEventListener("resize",()=>{
  2. renderer.setSize(window.innerWidth, window.innerHeight)
  3. camera.aspect = window.innerWidth / window.innerHeight
  4. camera.updateProjectionMatrix()
  5. })

导入轨道控制器

  1. // 添加轨道控制器
  2. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
  3. // 添加控制器
  4. const controls = new OrbitControls(camera, renderer.domElement)
  5. controls.enabled = true // 设置控制是否可用
  6. // 设置缩放范围
  7. controls.minDistance = zAxisNumber // 设置最小缩放距离
  8. controls.maxDistance = zAxisNumber + 500 // 设置最大缩放距离
  9. controls.enableDamping = true // 设置控制阻尼

设置渲染函数:  

  1. // 设置渲染函数
  2. const render = (time) =>{
  3. controls.update()
  4. renderer.render(scene,camera)
  5. requestAnimationFrame(render)
  6. }

页面加载调用

  1. <template>
  2. <div class="loginBg">
  3. <div class="login" ref="login"></div>
  4. </div>
  5. </template>
  6. <script setup>
  7. import { ref, onMounted } from 'vue'
  8. // 获取div实例对象
  9. let login = ref(null)
  10. onMounted(() => {
  11. login.value.appendChild(renderer.domElement) // 添加渲染器到div中
  12. render()
  13. })
  14. </script>

ok,写完基础代码之后,接下来开始具体的Demo实操。  

添加背景与地球

接下来开始加载背景贴图和地球模型,首先我们需要先将背景和地球的贴图导出来:

  1. // 加载图片
  2. const SKYIMG = new URL('../../assets/images/sky.png', import.meta.url).href
  3. const EARTHIMG = new URL('../../assets/images/earth_bg.png', import.meta.url).href

接下来我们借助three提供的TextureLoader库用于加载和处理纹理,给物体表面增添细节和真实感的图像渲染3D场景中的物体,如下:

  1. // 添加背景
  2. let texture = new THREE.TextureLoader().load(SKYIMG)
  3. const geometry = new THREE.BoxGeometry(window.innerWidth, window.innerHeight, 1400) // 创建立方体
  4. const material = new THREE.MeshBasicMaterial({ // 创建材质
  5. map: texture, // 纹理贴图
  6. side: THREE.BackSide, // 背面
  7. })
  8. const mesh = new THREE.Mesh(geometry, material) // 创建网格模型
  9. scene.add(mesh) // 添加到场景

因为我设置了控制器的缩放范围,使得我们只能在生成的背景盒子里面转动:

接下来添加地球模型,这里仍然采用贴图的模型进行:

  1. // 添加球体
  2. let SphereTexture = new THREE.TextureLoader().load(EARTHIMG)
  3. const SphereGeometry = new THREE.SphereGeometry(50, 60, 32) // 创建球体几何体
  4. const Spherematerial = new THREE.MeshPhongMaterial()
  5. Spherematerial.map = new THREE.TextureLoader().load(EARTHIMG) // 创建材质
  6. const sphere = new THREE.Mesh(SphereGeometry, Spherematerial) // 创建网格模型
  7. sphere.position.set(-400, 200, -200) // 设置位置
  8. scene.add(sphere) // 添加到场景

达到的效果如下,因为场景是没有光源的,所有加载的模型是黑色的,没问题。

接下来我们给场景添加环境光和点光源,让加载的模型有点反光的效果,代码如下:

  1. // 添加光源
  2. const ambientlight = new THREE.AmbientLight(0xffffff, 1) // 创建环境光源
  3. scene.add(ambientlight) // 添加到场景
  4. const positLight = new THREE.PointLight(0x0655fd, 5, 0) // 创建点光源
  5. positLight.position.set(0, 200,100) // 设置位置
  6. scene.add(positLight) // 添加到场景

这里顺便设置一下地球模型的自传效果,代码如下我们在render渲染函数中调用一下:

  1. // 球体自转
  2. const renderSphereRotate = () => {
  3. sphere.rotateY(0.001)
  4. }
  5. // 设置渲染函数
  6. const render = () =>{
  7. controls.update()
  8. renderer.render(scene, camera)
  9. requestAnimationFrame(render)
  10. renderSphereRotate() // 自转
  11. }

最终呈现的效果如下:

星星动画效果

接下来我们给正对我们电脑屏幕的角度后面添加星星,让其向着我们的角度进行运动,这里我们要先引入一下星星的图片,这里准备了两张星星的图片,如下:

  1. const STAR1IMG = new URL('../../assets/images/starflake1.png', import.meta.url).href
  2. const STAR2IMG = new URL('../../assets/images/starflake2.png', import.meta.url).href

这里开始处理星星的坐标、数量以及材质效果,如下:

  1. // 初始位置
  2. let initZposition = -zAxisNumber - 1400
  3. // 声明点的参数
  4. let parameters = []
  5. let materials = [] // 创建材质数组
  6. // 初始化星星效果
  7. const initSceneStar = (initZposition) => {
  8. const starGeometry = new THREE.BufferGeometry()
  9. // 星星位置的坐标
  10. const starPosition = []
  11. // 创建纹理
  12. const starTexture = new THREE.TextureLoader()
  13. const sprite1 = starTexture.load(STAR1IMG)
  14. const sprite2 = starTexture.load(STAR2IMG)
  15. // 星星的数据
  16. const pointsGeometry = []
  17. parameters = [
  18. [ [0.6, 100, 0.75], sprite1, 50 ],
  19. [ [0, 0, 1], sprite2, 20 ],
  20. ]
  21. // 创建1500颗星星
  22. for (let i = 0; i < 1500; i++) {
  23. const x = THREE.MathUtils.randFloatSpread(window.innerWidth) // 随机x坐标
  24. const y = _.random(0, window.innerHeight / 2) // 随机y坐标
  25. const z = _.random(-1400 / 2, zAxisNumber) // 随机z坐标
  26. starPosition.push(x, y, z) // 添加到坐标数组
  27. }
  28. starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starPosition, 3)) // 设置坐标
  29. // 创建两种不同的材质
  30. for (let i = 0; i < parameters.length; i++) {
  31. const color = parameters[i][0] // 颜色
  32. const sprite = parameters[i][1] // 纹理
  33. const size = parameters[i][2] // 点的大小
  34. materials[i] = new THREE.PointsMaterial({ // 创建材质
  35. size: size, // 点的大小
  36. map: sprite, // 纹理
  37. blending: THREE.AdditiveBlending, // 混合模式
  38. transparent: true, // 背景透明
  39. depthTest: true, // 深度测试
  40. })
  41. materials[i].color.setHSL(color[0], color[1], color[2]) // 设置颜色
  42. const points = new THREE.Points(starGeometry, materials[i]) // 创建点
  43. points.rotation.x = Math.random() * 0.2 - 0.15 // 随机旋转x轴
  44. points.rotation.y = Math.random() * 0.2 - 0.15 // 随机旋转y轴
  45. points.rotation.z = Math.random() * 0.2 - 0.15 // 随机旋转z轴
  46. points.position.setZ(initZposition) // 设置z轴位置
  47. pointsGeometry.push(points) // 添加到数组
  48. scene.add(points) // 添加到场景
  49. }
  50. return pointsGeometry // 返回数组
  51. }

最终呈现的效果如下:

接下来我们要对这些星星进行处理,让其运动起来。这里对两种星星的数据进行处理:

  1. // 声明点1,点2在z轴上的移动位置
  2. let zprogress1 = -zAxisNumber - 1400 / 2
  3. let zprogress2 = (-zAxisNumber - 1400 / 2) * 2
  4. // 声明点1,点2的数据
  5. let zprogress_first = []
  6. let zprogress_second = []
  7. // 渲染星星的运动效果
  8. const renderStarMove = () => {
  9. // 星星颜色交替变化
  10. const time = Date.now() * 0.00005 // 获取当前时间
  11. for (let i = 0; i < parameters.length; i++) {
  12. const color = parameters[i][0] // 颜色
  13. const h = ((360 * (color[0] + time)) % 360) / 360 // 计算颜色
  14. materials[i].color.setHSL(color[0], color[1], parseFloat(h.toFixed(2))) // 设置颜色
  15. }
  16. // 完成星星的运动
  17. zprogress1 += 1
  18. zprogress2 += 1
  19. // 判断点1,点2是否到达边界
  20. if (zprogress1 >= zAxisNumber + 1400 / 2) {
  21. zprogress1 = -zAxisNumber - 1400 / 2
  22. } else {
  23. zprogress_first.forEach(item => {
  24. item.position.setZ(zprogress1) // 设置z轴位置
  25. })
  26. }
  27. if (zprogress2 >= zAxisNumber + 1400 / 2) {
  28. zprogress2 = -zAxisNumber - 1400 / 2
  29. } else {
  30. zprogress_second.forEach(item => {
  31. item.position.setZ(zprogress2) // 设置z轴位置
  32. })
  33. }
  34. }

在页面刚加载的时候将获取星星的位置数据,然后在渲染函数中进行调用即可:

  1. onMounted(() => {
  2. login.value.appendChild(renderer.domElement) // 添加渲染器到div中
  3. initSceneStar(initZposition) // 初始化星星
  4. zprogress_first = initSceneStar(zprogress1) // 初始化点1
  5. zprogress_second = initSceneStar(zprogress2) // 初始化点2
  6. render()
  7. })
  8. // 设置渲染函数
  9. const render = () =>{
  10. controls.update()
  11. renderer.render(scene, camera)
  12. requestAnimationFrame(render)
  13. renderSphereRotate() // 自转
  14. renderStarMove() // 星星移动
  15. }

最终实现的效果如下,总体来说还是不错的:

星云动画效果

加载星云的效果和上面讲述的星星类似,这里我们仍然要借助一个贴图来实现,具体代码如下:

  1. // 渲染星云的效果
  2. const renderCloud = (width, height) => {
  3. const texture = new THREE.TextureLoader().load(CLOUDIMG) // 加载纹理
  4. const geometry = new THREE.PlaneGeometry(width, height) // 创建平面
  5. const material = new THREE.MeshBasicMaterial({ // 创建材质
  6. map: texture, // 纹理
  7. side: THREE.DoubleSide, // 渲染双面
  8. transparent: true, // 透明
  9. depthTest: false, // 深度测试
  10. blending: THREE.AdditiveBlending, // 混合模式
  11. })
  12. const plane = new THREE.Mesh(geometry, material) // 创建平面
  13. scene.add(plane) // 添加到场景
  14. return plane
  15. }

在onMounted中调用该函数然后传入宽高即可,如下可以看到我们的星云已经加载出来了:

接下来我们给星云设置运动效果,这里借助three中的CatmullRomCurve3创建3维曲线:

  1. // 渲染星云的运动效果
  2. const renderCloudMove = (cloud, route, speed) => {
  3. let cloudProgress = 0 // 星云位置
  4. // 创建三维曲线
  5. const curve = new THREE.CatmullRomCurve3(route)
  6. // 创建星云的运动轨迹
  7. return () => {
  8. if(cloudProgress <= 1) {
  9. cloudProgress += speed
  10. const point = curve.getPoint(cloudProgress) // 获取当前位置
  11. if (point && point.x) {
  12. cloud.position.set(point.x, point.y, point.z) // 设置位置
  13. }
  14. } else {
  15. cloudProgress = 0
  16. }
  17. }
  18. }

在onMounted中我们调用两片星云被返回两个函数:

  1. onMounted(() => {
  2. login.value.appendChild(renderer.domElement) // 添加渲染器到div中
  3. initSceneStar(initZposition) // 初始化星星
  4. zprogress_first = initSceneStar(zprogress1) // 初始化点1
  5. zprogress_second = initSceneStar(zprogress2) // 初始化点2
  6. cloudFirst = renderCloud(400, 200)
  7. cloudSecond = renderCloud(200, 100)
  8. renderCloudFirst = renderCloudMove(cloudFirst, [
  9. new THREE.Vector3(-window.innerWidth / 10, 0, -1400 / 2),
  10. new THREE.Vector3(-window.innerWidth / 4, window.innerHeight / 8, 0),
  11. new THREE.Vector3(-window.innerWidth / 4, 0, zAxisNumber),
  12. ], 0.0002)
  13. renderCloudSecond = renderCloudMove(cloudSecond, [
  14. new THREE.Vector3(window.innerWidth / 8, window.innerHeight / 8, -1400 / 2),
  15. new THREE.Vector3(window.innerWidth / 8, window.innerHeight / 8, zAxisNumber),
  16. ], 0.0008)
  17. render()
  18. })

在渲染函数中,我们调用这两个函数,进行星云的运动:

  1. // 创建两片星云
  2. let cloudFirst
  3. let cloudSecond
  4. // 创建星云的渲染函数
  5. let renderCloudFirst
  6. let renderCloudSecond
  7. // 设置渲染函数
  8. const render = () =>{
  9. controls.update()
  10. renderer.render(scene, camera)
  11. requestAnimationFrame(render)
  12. renderSphereRotate() // 自转
  13. renderStarMove() // 星星移动
  14. renderCloudFirst() // 云1运动
  15. renderCloudSecond() // 云2运动
  16. }

最终达到的效果如下:

实现登录框效果

 ok,3D背景已经完成了,这里我们将上面实现的three场景单独放在一个新的组件login-bg中,在该组件中我们设置一下一个大的背景图片,通过定位设置在底部即可,如下:

  1. <template>
  2. <div class="loginBg">
  3. <div class="login" ref="login"></div>
  4. <div class="bg"></div>
  5. </div>
  6. </template>
  7. <style scoped lang="scss">
  8. .loginBg {
  9. width: 100%;
  10. height: 100vh;
  11. position: relative;
  12. .login {
  13. width: 100%;
  14. height: 100%;
  15. }
  16. .bg {
  17. position: absolute;
  18. width: 100%;
  19. height: 400px;
  20. background-image: url("../../assets/images/ground.png");
  21. bottom: 0;
  22. }
  23. }
  24. </style>

效果如下:

ok,接下来我们开始设置表单登录框的样式了,效果还不错:

接下来设置一个背景人物,让其悬浮在登录框的右上角,然后通过css动画使其上下运动:

css样式如下:

  1. // 背景人物
  2. .moving-image {
  3. position: absolute;
  4. top: 120px; /* 初始位置 */
  5. left: 1000px; /* 初始位置 */
  6. z-index: 1000;
  7. animation: moveUpDown 2s infinite; /* 应用动画,循环播放 */
  8. }
  9. @keyframes moveUpDown {
  10. 0% { transform: translateY(0); }
  11. 50% { transform: translateY(-20px); }
  12. 100% { transform: translateY(0); }
  13. }

最终的效果如下,有那味了!

后面收集一下表单数据,判断登录然后进行页面跳转即可:

  1. // 收集表单数据
  2. let loginForm = reactive({
  3. username: '',
  4. password: '',
  5. })
  6. let router = useRouter()
  7. // 路由跳转
  8. const goHome = () => {
  9. if (loginForm.username === '' || loginForm.password === '') {
  10. ElNotification({
  11. type: 'error',
  12. message: "用户名和密码不能为空",
  13. })
  14. } else {
  15. router.push('/home')
  16. }
  17. }

效果如下,后面根据token判断登录即可,这里不再赘述,OK,效果达成!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/435156
推荐阅读
相关标签
  

闽ICP备14008679号