提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
本文讲介绍如何通过Vue2实现导入3D模型和小陨石飞行效果的登录页
通过本篇文章你将会学习到
1.vue ui 项目创建过程
2.登录页面的实现
3.路由拦截实现
4.基础的threejs知识
5.threejs如何导入3d建模
6.threejs 如何实现动画效果
一、准备
1.创建一个Vue项目:首先,确保你已经安装了Vue CLI。然后,在命令行中运行以下命令来创建一个新的Vue项目,需要使用router:
vue create my-threejs-app
2.安装Three.js:进入项目文件夹并安装Three.js依赖。在命令行中运行以下命令:
cd my-threejs-app
npm install three
3.在ThreeModel.vue文件中,使用import语句导入Three.js库:
import * as THREE from 'three';
二、项目创建
创建一个Vue项目:首先,确保你已经安装了Vue CLI。然后,在命令行中运行以下命令来创建一个新的Vue项目:
vue create my-threejs-app
使用 vue ui
指令进入项目导入项目并安装 router
和 less
依赖,操作如下
在插件中添加 vue-router
,如果需要使用vuex
也可以添加进去,此处就一并添加了。
在依赖中搜索 less
和 less-loader
并添加
此时项目就已经创建完成了,可以运行我们的项目了,运行指令如下,可通过page.json查看配置:
npm run serve
项目中使用到的组件库为ivew
官网地址(iview安装地址)
npm install view-design --save
安装后在 main.js
中引入
import ViewUI from 'view-design';
import 'view-design/dist/styles/iview.css';
Vue.use(ViewUI);
二、路由搭建
1.将App.vue页面不需要的代码删除,代码如下:
<template>
<div id="app">
<router-view />
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin: 0;
padding: 0;
}
</style>
2.创建登录页面,并在router中注册
<template>
<div>hello world</div>
</template>
<script>
export default {
name: 'appLogin',
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="less" scoped></style>
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/login",
name: "login",
component: () =>
import('../views/login/index.vue'),
meta: {
title: "登录",
ispublic: true
}
},
];
const router = new VueRouter({
routes,
});
export default router;
此时你会发现页面是空白的,因为页面路由为 / ,如果手动输入/login 就可以跳转到登录页,但这样做是不行的,所以需要使用到路由守卫 router.beforeEach
// 全局路由守卫,如果不是登录状态,则到登录页面 路由拦截
router.beforeEach((to, from, next) => {
// 有token为登录状态
if (sessionStorage.token) {
console.log("__登录成功!_")
next();
} else {
if (to.path === "/login") {
next();
} else {
next("/login");
}
}
});
在路由守卫中可进行多种操作,如存储左侧菜单栏数据,数据拦截,单点登录等操作皆可在此处完成。
三、登陆页面搭建
代码如下对此就不做阐述了,代码中引入了 moving
滑块组件,代码也在下面可以直接使用。
目录格式如下
登录页代码 :
<template>
<div class="wrap">
<div id="convas-container"></div>
<div class="container">
<Card :bordered="false" class="cardstyle" v-if="islogin">
<p slot="title" style="text-align: center; color: #c9c7c7; font-size: 21px">
杂七杂八后台
</p>
<div style="display: flex; justify-content: cneter">
<Input v-model="model.username" placeholder="账号" style="width: 300px; margin: 0 auto" />
</div>
<div style="display: flex; justify-content: cneter">
<Input v-model="model.password" placeholder="密码" style="width: 300px; margin: 0 auto" type="password" @on-enter="handleLogin"/>
</div>
<div style="text-align: center;">自动登录</div>
<div>
<Row>
<Col span="16">
<div style="
color: white;
margin-left: 40px;
float: left;
line-height: 30px;
">
其他登录方式:
</div>
<img src="../../assets/login/qq.svg" class="otherLoginImg" />
<img src="../../assets/login/weixin.svg" class="otherLoginImg" />
<img src="../../assets/login/weibo.svg" class="otherLoginImg" />
</Col>
<Col span="8">
<a href="#" @click="handleToNews"> 注册新账号 </a>
</Col>
</Row>
</div>
<div style="display: flex; justify-content: cneter">
<Button type="primary" style="width: 320px; margin: 10px auto"
@click="handleLogin" >登 录</Button>
</div>
<p class="change_link" style="text-align: center">
<span class="text">忘记密码了 ?</span>
<a href="#" class="to_login"> 点击此处 </a>
</p>
</Card>
<Card :bordered="false" class="cardstyle" v-else>
<p slot="title" style="text-align: center; color: #111; font-size: 21px">
后台管理系统
</p>
<div style="display: flex; justify-content: cneter">
<Input v-model="username" placeholder="账号" style="width: 300px; margin: 0 auto" />
</div>
<div style="display: flex; justify-content: cneter">
<Input v-model="password" placeholder="密码" style="width: 300px; margin: 0 auto" type="password" />
</div>
<div style="display: flex; justify-content: cneter">
<Input v-model="passwordAgain" placeholder="确认密码" style="width: 300px; margin: 0 auto" type="password" />
</div>
<moving></moving>
<div style="display: flex; justify-content: cneter">
<Button type="primary" style="width: 320px; margin: 5px auto" @click="handleToRegister"
:disabled="!confirmSuccess">注 册</Button>
</div>
<p class="change_link" style="text-align: center">
<a href="#" class="to_login" @click="handleToUse">
使用已有账户登录
</a>
</p>
</Card>
</div>
</div>
</template>
<script>
import { mapActions, mapState, mapMutations } from "vuex";
import moving from "./component/moving";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
// 导入 gltf 加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import gsap from "gsap"
export default {
data() {
return {
islogin: true,
model: {},
username: "",
password: "",
passwordAgain: "",
};
},
computed: {
confirmSuccess() {
return this.$store.state.login.confirmSuccess;
},
dataCount() {
return this.$store.state.login.user;
},
},
components: { moving },
methods: {
...mapActions([
"postLogin",
"postRegister",
]),
...mapMutations(["setLogin"]),
// 有材质的球体
fnNewRedios(textureImg, radius) {
const geometry = new THREE.SphereGeometry(radius, 32, 12);
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load(textureImg);
texture.colorSpace = THREE.LinearSRGBColorSpace; //转换为线性srgb色 色差有问题使用这个
const material = new THREE.MeshBasicMaterial({ map: texture, });
return new THREE.Mesh(geometry, material);
},
fn3dX7() {
let width = window.innerWidth; //窗口宽度
let height = window.innerHeight; //窗口高度RGBELoader
let k = width / height; //窗口宽高比
let s = 400; //三维场景显示范围控制系数,系数越大,显示的范围越大
let DOM = document.getElementById("convas-container");
// 创建画布
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer(
{
antialias: true, // 设置抗锯齿
}
);
renderer.setSize(width, height);
renderer.setClearColor("#000");
// 创建背景
let url = "../../assets/a.png";
let nevTexture = new THREE.TextureLoader().load(url);
// nevTexture.mapping = THREE.EquirectangularReflectionMapping;//(全景图反射贴图)是一种材质映射方式,用于将全景图作为反射贴图应用到一个物体上
scene.background = nevTexture;
scene.environment = nevTexture;
// 添加建模
const gltfLoad = new GLTFLoader();
let cubeGlb = null;
gltfLoad.load(
"./model/x7.glb",
// 加载建模完的回调函数
(gltf) => {
cubeGlb = gltf.scene;
cubeGlb.position.set(-30, 0, 0);
gltf.scene.traverse((child) => {
if (child.isMesh) {
// 修改材质属性
// 其他修改材质的操作...
if (child.name === "plane") {
child.visible = false;
}
// 允许接受投射阴影
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
}
});
scene.add(cubeGlb)
}
)
// 添加灯光
let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-40, 20, -10);
scene.add(light);
let light1 = new THREE.DirectionalLight(0xffffff, 0.6);
light1.position.set(-30, 20, 10);
scene.add(light1);
let light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 10, -10);
scene.add(light2)
light.castShadow = true; // 投射阴影
light1.castShadow = true; // 投射阴影
light2.castShadow = true; // 投射阴影
// 添加月亮小星球
let moon = this.fnNewRedios("./img/yueliang.jpg", 1);
//一种特殊类型的网格对象。它允许你在场景中创建大量的实例对象,而只需使用一个几何体和材质来渲染它们。
for (let j = 0; j < 5; j++) {
let moonInstance = new THREE.InstancedMesh(
moon.geometry,
moon.material,
50
);
for (let i = 0; i < 1000; i++) {
let x = Math.random() * 1000 - 500;
let y = Math.random() * 1000 - 500;
let z = Math.random() * 1000 - 500;
let matrix = new THREE.Matrix4;
matrix.makeTranslation(x, y, z);
moonInstance.setMatrixAt(i, matrix);
}
gsap.to(moonInstance.position, {
duration: Math.random() * 10 + 4,
z: -1001,
ease: "linear",
repeat: -1,
})
scene.add(moonInstance);
}
// 相机位置
const camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000);
camera.position.set(0, 0, 80); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
window.addEventListener('mousemove', (e) => {
let x = (e.clientX / window.innerWidth) * 0.4;
let y = (e.clientY / window.innerHeight) * 0.2;
let timeline = gsap.timeline();
timeline.to(cubeGlb.rotation, {
duration: 0.5,
y: x,
x: y,
})
})
// 添加控制器
// const controls = new OrbitControls(camera, DOM);
// Dom添加
DOM.appendChild(renderer.domElement);
// 渲染场景
(function fnAnimate(time) {
// controls.update();
renderer.render(scene, camera)
requestAnimationFrame(fnAnimate);
})()
},
// 登录
async handleLogin() {
this.postLogin(
this.model,
).then((res) => {
console.log(res)
if (res.code === 3003) {
this.$Message.error({
content: "账号错误",
type: "error",
});
} else if (res.code === 3004) {
this.$Message.error({
content: "密码错误",
type: "error",
});
} else {
sessionStorage.token = "success";
this.$Message.success({
content: "登陆成功",
type: "success",
});
console.log("___res.data__", res.data)
this.setLogin({
user: res.data
})
this.$router.push({ name: "dataMaintain" });
}
});
},
handleToRegister() {
this.postRegister({
username: this.username,
password: this.password
}).then((res) => {
if (res.data.code === 200) {
// sessionStorage.token = res.data.token;
this.$Message.success({
content: "登陆成功",
type: "success",
});
this.$router.push({ name: "termSetting" });
} else {
this.$Message({
content: "登陆失败",
type: "error",
})
}
})
},
handleToNews() {
this.islogin = false;
this.password = "";
this.user = "";
},
handleToUse() {
this.islogin = true;
},
handletoNewUser() { },
},
mounted() {
this.fn3dX7()
},
};
</script>
<style lang="less">
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.wrap {
width: 100%;
height: 100%;
// padding: 10% 0;
position: fixed;
background: url("../../assets/a.png") no-repeat;
background-size: 100% 100%;
background: -webkit-linear-gradient(to bottom right, #50a3a2, #53e3a6);
}
.opacityPage {
background: #041656;
opacity: 0.5;
border-radius: 40px 4px 4px 4px;
width: 40%;
height: 400px;
margin: 0 auto;
}
.cardstyle {
width: 500px;
margin: 0 auto;
background: linear-gradient(to right, rgba(79, 120, 195, 0.25), rgba(209, 226, 240, 0.4));
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
backdrop-filter: blur(4.5px);
-webkit-backdrop-filter: blur(4.5px);
border-radius: 10px;
.otherLoginImg {
width: 30px;
height: 30px;
padding: 0 5px;
}
}
.container {
z-index: 10;
position: absolute;
top: 50%;
left: 65%;
transform: translate(0, -50%);
}
.container h1 {
text-align: center;
color: #ffffff;
font-weight: 500;
}
.container input {
border: 0;
outline: 0;
line-height: 24px;
margin: 32px auto;
}
.to_login {
color: #a7c4c9;
}
.text {
color: #e2dfe4;
}
.wrap ul {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -20;
}
.wrap ul li {
list-style-type: none;
display: block;
position: absolute;
bottom: -120px;
width: 15px;
height: 15px;
z-index: -8;
border-radius: 50%;
box-shadow: inset -30px -30px 75px rgba(0, 0, 0, 0.2),
inset 0px 0px 5px rgba(0, 0, 0, 0.5), inset -3px -3px 5px #089cef,
0 0 20px rgba(255, 255, 255, 0.75);
background-image: radial-gradient(#d6e6ff, #77b5fe);
animotion: square 25s infinite;
-webkit-animation: square 25s infinite;
}
.wrap ul li:nth-child(1) {
left: 0;
animation-duration: 10s;
-moz-animation-duration: 10s;
-o-animation-duration: 10s;
-webkit-animation-duration: 10s;
}
.wrap ul li:nth-child(2) {
width: 40px;
height: 40px;
left: 10%;
animation-duration: 15s;
-moz-animation-duration: 15s;
-o-animation-duration: 15s;
-webkit-animation-duration: 11s;
}
.wrap ul li:nth-child(3) {
left: 20%;
width: 25px;
height: 25px;
animation-duration: 12s;
-moz-animation-duration: 12s;
-o-animation-duration: 12s;
-webkit-animation-duration: 12s;
}
.wrap ul li:nth-child(4) {
width: 50px;
height: 50px;
left: 30%;
-webkit-animation-delay: 3s;
-moz-animation-delay: 3s;
-o-animation-delay: 3s;
animation-delay: 3s;
animation-duration: 12s;
-moz-animation-duration: 12s;
-o-animation-duration: 12s;
-webkit-animation-duration: 12s;
}
.wrap ul li:nth-child(5) {
width: 60px;
height: 60px;
left: 40%;
animation-duration: 10s;
-moz-animation-duration: 10s;
-o-animation-duration: 10s;
-webkit-animation-duration: 10s;
}
.wrap ul li:nth-child(6) {
width: 75px;
height: 75px;
left: 50%;
-webkit-animation-delay: 7s;
-moz-animation-delay: 7s;
-o-animation-delay: 7s;
animation-delay: 7s;
}
.wrap ul li:nth-child(7) {
left: 60%;
width: 30px;
height: 30px;
animation-duration: 8s;
-moz-animation-duration: 8s;
-o-animation-duration: 8s;
-webkit-animation-duration: 8s;
}
.wrap ul li:nth-child(8) {
width: 90px;
height: 90px;
left: 70%;
-webkit-animation-delay: 4s;
-moz-animation-delay: 4s;
-o-animation-delay: 4s;
animation-delay: 4s;
}
.wrap ul li:nth-child(9) {
width: 50px;
height: 50px;
left: 80%;
animation-duration: 20s;
-moz-animation-duration: 20s;
-o-animation-duration: 20s;
-webkit-animation-duration: 20s;
}
.wrap ul li:nth-child(10) {
width: 75px;
height: 75px;
left: 90%;
-webkit-animation-delay: 6s;
-moz-animation-delay: 6s;
-o-animation-delay: 6s;
animation-delay: 6s;
animation-duration: 30s;
-moz-animation-duration: 30s;
-o-animation-duration: 30s;
-webkit-animation-duration: 30s;
}
@keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
bottom: 400px;
-webkit-transform: translateY(-500);
transform: translateY(-500);
}
}
@-webkit-keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
bottom: 400px;
-webkit-transform: translateY(-500);
transform: translateY(-500);
}
}
</style>
滑块组件代码如下:
<template>
<div class="drag" ref="dragDiv">
<div class="drag_bg"></div>
<div class="drag_text">{{ confirmWords }}</div>
<div
ref="moveDiv"
@mousedown="mousedownFn($event)"
:class="{ handler_ok_bg: confirmSuccess }"
class="handler handler_bg"
style="position: absolute; top: 0px; left: 0px"
></div>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
name: "movingComponent",
data() {
return {
beginClientX: 0 /*距离屏幕左端距离*/,
mouseMoveStata: false /*触发拖动状态 判断*/,
maxwidth: "" /*拖动最大宽度,依据滑块宽度算出来的*/,
confirmWords: "拖动滑块验证" /*滑块文字*/,
confirmSuccess: false /*验证成功判断*/,
};
},
computed: {},
methods: {
...mapMutations(["setLogin"]),
mousedownFn: function (e) {
if (!this.confirmSuccess) {
e.preventDefault && e.preventDefault(); //阻止文字选中等 浏览器默认事件
this.mouseMoveStata = true;
this.beginClientX = e.clientX;
}
}, //mousedoen 事件
successFunction() {
this.$emit("fnMovingSuccess", this.mouseMoveStata);
this.confirmWords = "验证通过";
if (window.addEventListener) {
document
.getElementsByTagName("html")[0]
.removeEventListener("mousemove", this.mouseMoveFn);
document
.getElementsByTagName("html")[0]
.removeEventListener("mouseup", this.moseUpFn);
} else {
document
.getElementsByTagName("html")[0]
.removeEventListener("mouseup", () => {});
}
document.getElementsByClassName("drag_text")[0].style.color = "#fff";
document.getElementsByClassName("handler")[0].style.left =
this.maxwidth + "px";
document.getElementsByClassName("drag_bg")[0].style.width =
this.maxwidth + "px";
}, //验证成功函数
mouseMoveFn(e) {
if (this.mouseMoveStata) {
let width = e.clientX - this.beginClientX;
if (width > 0 && width <= this.maxwidth) {
document.getElementsByClassName("handler")[0].style.left =
width + "px";
document.getElementsByClassName("drag_bg")[0].style.width =
width + "px";
} else if (width > this.maxwidth) {
this.successFunction();
}
}
}, //mousemove事件
moseUpFn(e) {
this.mouseMoveStata = false;
var width = e.clientX - this.beginClientX;
if (width < this.maxwidth) {
document.getElementsByClassName("handler")[0].style.left = 0 + "px";
document.getElementsByClassName("drag_bg")[0].style.width = 0 + "px";
}
}, //mouseup事件
},
mounted() {
this.confirmSuccess = false;
this.maxwidth =
this.$refs.dragDiv.clientWidth - this.$refs.moveDiv.clientWidth;
document
.getElementsByTagName("html")[0]
.addEventListener("mousemove", this.mouseMoveFn);
document
.getElementsByTagName("html")[0]
.addEventListener("mouseup", this.moseUpFn);
},
};
</script>
<style scoped>
.drag {
position: relative;
background-color: #e8e8e8;
width: 80%;
height: 34px;
line-height: 34px;
text-align: center;
margin: 0 0 0 40px;
}
.handler {
width: 40px;
height: 32px;
border: 1px solid #ccc;
cursor: move;
}
.handler_bg {
background: #fff
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==")
no-repeat center;
}
.handler_ok_bg {
background: #fff
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==")
no-repeat center;
}
.drag_bg {
background-color: #7ac23c;
height: 34px;
width: 0px;
}
.drag_text {
position: absolute;
top: 0px;
width: 100%;
text-align: center;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
}
</style>
此时登录页面就已经搭建完成了
四、threejs场景搭建
如果没有install threejs 的需要先引入
npm install three -s
在页面中创建Three.js场景:创建一个Three.js的场景对象,并将其渲染到HTML元素上。你可以使用document.getElementById
属性来引用HTML元素。
<div id="convas-container"></div>
页面中引入 threejs
import * as THREE from "three";
fnGetTreejs() {
let width = window.innerWidth; //窗口宽度
let height = window.innerHeight; //窗口高度RGBELoader
let k = width / height; //窗口宽高比
// let s = 400; //三维场景显示范围控制系数,系数越大,显示的范围越大
let DOM = document.getElementById("convas-container");
// 创建画布
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({
antialias: true, // 设置抗锯齿
});
renderer.setSize(width, height);
renderer.setClearColor("#000");
// 创建背景
let url = "../../assets/login.png";
let nevTexture = new THREE.TextureLoader().load(url);
scene.background = nevTexture;
scene.environment = nevTexture;
// 相机位置
const camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000);
camera.position.set(0, 0, 80); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
// Dom添加
DOM.appendChild(renderer.domElement);
// 渲染场景
(function fnAnimate() {
// controls.update();
renderer.render(scene, camera)
requestAnimationFrame(fnAnimate);
})()
},
在mounted中调用
mounted() {
this.fnGetTreejs()
},
此时最基础的场景就已经搭建好了。目前需要的就是引入建模了!
五、引入建模并使建模能跟随鼠标晃动
本次使用的建模为 .glb
格式的建模,素材地址在最底下自取。
注意 建模文件需要放在 public
文件下才能读取到
通过 gltfLoad
引入建模
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
const gltfLoad = new GLTFLoader();
let cubeGlb = null;
gltfLoad.load(
"./model/x7.glb",
// 加载建模完的回调函数
(gltf) => {
cubeGlb = gltf.scene;
cubeGlb.position.set(-30, 0, 0);
gltf.scene.traverse((child) => {
if (child.isMesh) {
// 修改材质属性
// 其他修改材质的操作...
if (child.name === "plane") {
child.visible = false;
}
// 允许接受投射阴影
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
}
});
scene.add(cubeGlb);
}
);
此时建模就已经加载完成了,但你会发现建模是黑的,因为没有打光,这时候需要添加上光源照亮建模
下文中的代码我添加了三个点光源 并开启了阴影效果
// 添加灯光
let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-40, 20, -10);
scene.add(light);
let light1 = new THREE.DirectionalLight(0xffffff, 0.6);
light1.position.set(-30, 20, 10);
scene.add(light1);
let light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 10, -10);
scene.add(light2)
light.castShadow = true; // 投射阴影
light1.castShadow = true; // 投射阴影
light2.castShadow = true; // 投射阴影
建模导入完成了!
我想让建模能跟随鼠标一起移动,可以为 window
增加鼠标移动的监听事件 mousemove
, 并改变 建模 rotation
的 x
与 y
,
此时我们将会使用到 gsap
动画库
npm install gsap -s
import gsap from "gsap"
window.addEventListener('mousemove', (e) => {
let x = (e.clientX / window.innerWidth) * 0.4;
let y = (e.clientY / window.innerHeight) * 0.2;
let timeline = gsap.timeline();
timeline.to(cubeGlb.rotation, {
duration: 0.5,
y: x,
x: y,
})
})
此时建模就会跟随鼠标移动了!
fnGetTreejs()
完整代码:
fnGetTreejs() {
let width = window.innerWidth; //窗口宽度
let height = window.innerHeight; //窗口高度RGBELoader
let k = width / height; //窗口宽高比
// let s = 400; //三维场景显示范围控制系数,系数越大,显示的范围越大
let DOM = document.getElementById("convas-container");
// 创建画布
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({
antialias: true, // 设置抗锯齿
});
renderer.setSize(width, height);
renderer.setClearColor("#000");
// 创建背景
let url = "../../assets/login.png";
let nevTexture = new THREE.TextureLoader().load(url);
scene.background = nevTexture;
scene.environment = nevTexture;
// 添加灯光
let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-40, 20, -10);
scene.add(light);
let light1 = new THREE.DirectionalLight(0xffffff, 0.6);
light1.position.set(-30, 20, 10);
scene.add(light1);
let light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 10, -10);
scene.add(light2);
light.castShadow = true; // 投射阴影
light1.castShadow = true; // 投射阴影
light2.castShadow = true; // 投射阴影
// 添加建模
const gltfLoad = new GLTFLoader();
let cubeGlb = null;
gltfLoad.load(
"./model/x7.glb",
// 加载建模完的回调函数
(gltf) => {
cubeGlb = gltf.scene;
cubeGlb.position.set(-30, 0, 0);
gltf.scene.traverse((child) => {
if (child.isMesh) {
// 修改材质属性
// 其他修改材质的操作...
if (child.name === "plane") {
child.visible = false;
}
// 允许接受投射阴影
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
}
});
scene.add(cubeGlb);
}
);
// 相机位置
const camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000);
camera.position.set(0, 0, 80); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
window.addEventListener('mousemove', (e) => {
let x = (e.clientX / window.innerWidth) * 0.4;
let y = (e.clientY / window.innerHeight) * 0.2;
let timeline = gsap.timeline();
timeline.to(cubeGlb.rotation, {
duration: 0.5,
y: x,
x: y,
})
})
// Dom添加
DOM.appendChild(renderer.domElement);
// 渲染场景
(function fnAnimate() {
// controls.update();
renderer.render(scene, camera);
requestAnimationFrame(fnAnimate);
})();
},
六、流星效果
增加一个流星效果去美化页面,流星效果的实现实际上就是 绘制小圆球 改变其z轴位置
注意: 创建球体使用到的材质图片也需要放在public文件夹下
素材图片:
创建材质球体函数如下
// 有材质的球体
fnNewRedios(textureImg, radius) {
const geometry = new THREE.SphereGeometry(radius, 32, 12);
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load(textureImg);
texture.colorSpace = THREE.LinearSRGBColorSpace; //转换为线性srgb色 色差有问题使用这个
const material = new THREE.MeshBasicMaterial({ map: texture, });
return new THREE.Mesh(geometry, material);
},
在fnGetTreejs
函数中 我们可以通过 InstancedMesh
去绘制大量的实例对象,并通过gasp
线性改变 z 轴位置 流星 效果就实现了!
// 添加月亮小星球
let moon = this.fnNewRedios("./img/yueliang.jpg", 1);
//一种特殊类型的网格对象。它允许你在场景中创建大量的实例对象,而只需使用一个几何体和材质来渲染它们。
for (let j = 0; j < 5; j++) {
let moonInstance = new THREE.InstancedMesh(
moon.geometry,
moon.material,
50
);
for (let i = 0; i < 1000; i++) {
let x = Math.random() * 1000 - 500;
let y = Math.random() * 1000 - 500;
let z = Math.random() * 1000 - 500;
let matrix = new THREE.Matrix4;
matrix.makeTranslation(x, y, z);
moonInstance.setMatrixAt(i, matrix);
}
gsap.to(moonInstance.position, {
duration: Math.random() * 10 + 4,
z: -1001,
ease: "linear",
repeat: -1,
})
scene.add(moonInstance);
}
七、完整 login 代码如下
<template>
<div class="wrap">
<div id="convas-container"></div>
<div class="container">
<Card :bordered="false" class="cardstyle" v-if="islogin">
<p
slot="title"
style="text-align: center; color: #c9c7c7; font-size: 21px"
>
杂七杂八后台
</p>
<div style="display: flex; justify-content: cneter">
<Input
v-model="model.username"
placeholder="账号"
style="width: 300px; margin: 0 auto"
/>
</div>
<div style="display: flex; justify-content: cneter">
<Input
v-model="model.password"
placeholder="密码"
style="width: 300px; margin: 0 auto"
type="password"
@on-enter="handleLogin"
/>
</div>
<div style="text-align: center">自动登录</div>
<div>
<Row>
<Col span="16">
<div
style="
color: white;
margin-left: 40px;
float: left;
line-height: 30px;
"
>
其他登录方式:
</div>
<img src="../../assets/login/qq.svg" class="otherLoginImg" />
<img src="../../assets/login/weixin.svg" class="otherLoginImg" />
<img src="../../assets/login/weibo.svg" class="otherLoginImg" />
</Col>
<Col span="8">
<a href="#" @click="handleToNews"> 注册新账号 </a>
</Col>
</Row>
</div>
<div style="display: flex; justify-content: cneter">
<Button
type="primary"
style="width: 320px; margin: 10px auto"
@click="handleLogin"
>登 录</Button
>
</div>
<p class="change_link" style="text-align: center">
<span class="text">忘记密码了 ?</span>
<a href="#" class="to_login"> 点击此处 </a>
</p>
</Card>
<Card :bordered="false" class="cardstyle" v-else>
<p
slot="title"
style="text-align: center; color: #111; font-size: 21px"
>
后台管理系统
</p>
<div style="display: flex; justify-content: cneter">
<Input
v-model="username"
placeholder="账号"
style="width: 300px; margin: 0 auto"
/>
</div>
<div style="display: flex; justify-content: cneter">
<Input
v-model="password"
placeholder="密码"
style="width: 300px; margin: 0 auto"
type="password"
/>
</div>
<div style="display: flex; justify-content: cneter">
<Input
v-model="passwordAgain"
placeholder="确认密码"
style="width: 300px; margin: 0 auto"
type="password"
/>
</div>
<moving @fnMovingSuccess="fnMovingSuccess"></moving>
<div style="display: flex; justify-content: cneter">
<Button
type="primary"
style="width: 320px; margin: 5px auto"
@click="handleToRegister"
:disabled="!isMoveing"
>注 册</Button
>
</div>
<p class="change_link" style="text-align: center">
<a href="#" class="to_login" @click="handleToUse">
使用已有账户登录
</a>
</p>
</Card>
</div>
</div>
</template>
<script>
import moving from "./component/moving";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import gsap from "gsap"
export default {
name: "appLogin",
components: { moving },
data() {
return {
islogin: true,
model: {},
username: "",
password: "",
passwordAgain: "",
isMoveing: false,
};
},
methods: {
// 登录
handleLogin() {},
handleToRegister() {},
handleToNews() {
this.islogin = false;
this.password = "";
this.user = "";
},
handleToUse() {
this.islogin = true;
},
handletoNewUser() {},
// 有材质的球体
fnNewRedios(textureImg, radius) {
const geometry = new THREE.SphereGeometry(radius, 32, 12);
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load(textureImg);
texture.colorSpace = THREE.LinearSRGBColorSpace; //转换为线性srgb色 色差有问题使用这个
const material = new THREE.MeshBasicMaterial({ map: texture, });
return new THREE.Mesh(geometry, material);
},
fnGetTreejs() {
let width = window.innerWidth; //窗口宽度
let height = window.innerHeight; //窗口高度RGBELoader
let k = width / height; //窗口宽高比
// let s = 400; //三维场景显示范围控制系数,系数越大,显示的范围越大
let DOM = document.getElementById("convas-container");
// 创建画布
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({
antialias: true, // 设置抗锯齿
});
renderer.setSize(width, height);
renderer.setClearColor("#000");
// 创建背景
let url = "../../assets/login.png";
let nevTexture = new THREE.TextureLoader().load(url);
scene.background = nevTexture;
scene.environment = nevTexture;
// 添加灯光
let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-40, 20, -10);
scene.add(light);
let light1 = new THREE.DirectionalLight(0xffffff, 0.6);
light1.position.set(-30, 20, 10);
scene.add(light1);
let light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 10, -10);
scene.add(light2);
light.castShadow = true; // 投射阴影
light1.castShadow = true; // 投射阴影
light2.castShadow = true; // 投射阴影
// 添加建模
const gltfLoad = new GLTFLoader();
let cubeGlb = null;
gltfLoad.load(
"./model/x7.glb",
// 加载建模完的回调函数
(gltf) => {
cubeGlb = gltf.scene;
cubeGlb.position.set(-30, 0, 0);
gltf.scene.traverse((child) => {
if (child.isMesh) {
// 修改材质属性
// 其他修改材质的操作...
if (child.name === "plane") {
child.visible = false;
}
// 允许接受投射阴影
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
}
});
scene.add(cubeGlb);
}
);
// 添加月亮小星球
let moon = this.fnNewRedios("./img/yueliang.jpg", 1);
//一种特殊类型的网格对象。它允许你在场景中创建大量的实例对象,而只需使用一个几何体和材质来渲染它们。
for (let j = 0; j < 5; j++) {
let moonInstance = new THREE.InstancedMesh(
moon.geometry,
moon.material,
50
);
for (let i = 0; i < 1000; i++) {
let x = Math.random() * 1000 - 500;
let y = Math.random() * 1000 - 500;
let z = Math.random() * 1000 - 500;
let matrix = new THREE.Matrix4;
matrix.makeTranslation(x, y, z);
moonInstance.setMatrixAt(i, matrix);
}
gsap.to(moonInstance.position, {
duration: Math.random() * 10 + 4,
z: -1001,
ease: "linear",
repeat: -1,
})
scene.add(moonInstance);
}
// 相机位置
const camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000);
camera.position.set(0, 0, 80); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
window.addEventListener('mousemove', (e) => {
let x = (e.clientX / window.innerWidth) * 0.4;
let y = (e.clientY / window.innerHeight) * 0.2;
let timeline = gsap.timeline();
timeline.to(cubeGlb.rotation, {
duration: 0.5,
y: x,
x: y,
})
})
// Dom添加
DOM.appendChild(renderer.domElement);
// 渲染场景
(function fnAnimate() {
// controls.update();
renderer.render(scene, camera);
requestAnimationFrame(fnAnimate);
})();
},
fnMovingSuccess(value) {
this.isMoveing = value;
},
},
mounted() {
this.fnGetTreejs();
},
};
</script>
<style lang="less">
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.wrap {
width: 100%;
height: 100%;
// padding: 10% 0;
position: fixed;
background: url("../../assets/login.png") no-repeat;
background-size: 100% 100%;
background: -webkit-linear-gradient(to bottom right, #50a3a2, #53e3a6);
}
.opacityPage {
background: #041656;
opacity: 0.5;
border-radius: 40px 4px 4px 4px;
width: 40%;
height: 400px;
margin: 0 auto;
}
.cardstyle {
width: 500px;
margin: 0 auto;
background: linear-gradient(
to right,
rgba(79, 120, 195, 0.25),
rgba(209, 226, 240, 0.4)
);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
backdrop-filter: blur(4.5px);
-webkit-backdrop-filter: blur(4.5px);
border-radius: 10px;
.otherLoginImg {
width: 30px;
height: 30px;
padding: 0 5px;
}
}
.container {
z-index: 10;
position: absolute;
top: 50%;
left: 65%;
transform: translate(0, -50%);
}
.container h1 {
text-align: center;
color: #ffffff;
font-weight: 500;
}
.container input {
border: 0;
outline: 0;
line-height: 24px;
margin: 32px auto;
}
.to_login {
color: #a7c4c9;
}
.text {
color: #e2dfe4;
}
.wrap ul {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -20;
}
.wrap ul li {
list-style-type: none;
display: block;
position: absolute;
bottom: -120px;
width: 15px;
height: 15px;
z-index: -8;
border-radius: 50%;
box-shadow: inset -30px -30px 75px rgba(0, 0, 0, 0.2),
inset 0px 0px 5px rgba(0, 0, 0, 0.5), inset -3px -3px 5px #089cef,
0 0 20px rgba(255, 255, 255, 0.75);
background-image: radial-gradient(#d6e6ff, #77b5fe);
animotion: square 25s infinite;
-webkit-animation: square 25s infinite;
}
.wrap ul li:nth-child(1) {
left: 0;
animation-duration: 10s;
-moz-animation-duration: 10s;
-o-animation-duration: 10s;
-webkit-animation-duration: 10s;
}
.wrap ul li:nth-child(2) {
width: 40px;
height: 40px;
left: 10%;
animation-duration: 15s;
-moz-animation-duration: 15s;
-o-animation-duration: 15s;
-webkit-animation-duration: 11s;
}
.wrap ul li:nth-child(3) {
left: 20%;
width: 25px;
height: 25px;
animation-duration: 12s;
-moz-animation-duration: 12s;
-o-animation-duration: 12s;
-webkit-animation-duration: 12s;
}
.wrap ul li:nth-child(4) {
width: 50px;
height: 50px;
left: 30%;
-webkit-animation-delay: 3s;
-moz-animation-delay: 3s;
-o-animation-delay: 3s;
animation-delay: 3s;
animation-duration: 12s;
-moz-animation-duration: 12s;
-o-animation-duration: 12s;
-webkit-animation-duration: 12s;
}
.wrap ul li:nth-child(5) {
width: 60px;
height: 60px;
left: 40%;
animation-duration: 10s;
-moz-animation-duration: 10s;
-o-animation-duration: 10s;
-webkit-animation-duration: 10s;
}
.wrap ul li:nth-child(6) {
width: 75px;
height: 75px;
left: 50%;
-webkit-animation-delay: 7s;
-moz-animation-delay: 7s;
-o-animation-delay: 7s;
animation-delay: 7s;
}
.wrap ul li:nth-child(7) {
left: 60%;
width: 30px;
height: 30px;
animation-duration: 8s;
-moz-animation-duration: 8s;
-o-animation-duration: 8s;
-webkit-animation-duration: 8s;
}
.wrap ul li:nth-child(8) {
width: 90px;
height: 90px;
left: 70%;
-webkit-animation-delay: 4s;
-moz-animation-delay: 4s;
-o-animation-delay: 4s;
animation-delay: 4s;
}
.wrap ul li:nth-child(9) {
width: 50px;
height: 50px;
left: 80%;
animation-duration: 20s;
-moz-animation-duration: 20s;
-o-animation-duration: 20s;
-webkit-animation-duration: 20s;
}
.wrap ul li:nth-child(10) {
width: 75px;
height: 75px;
left: 90%;
-webkit-animation-delay: 6s;
-moz-animation-delay: 6s;
-o-animation-delay: 6s;
animation-delay: 6s;
animation-duration: 30s;
-moz-animation-duration: 30s;
-o-animation-duration: 30s;
-webkit-animation-duration: 30s;
}
@keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
bottom: 400px;
-webkit-transform: translateY(-500);
transform: translateY(-500);
}
}
@-webkit-keyframes square {
0% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
100% {
bottom: 400px;
-webkit-transform: translateY(-500);
transform: translateY(-500);
}
}
</style>
八、使用到的素材
链接:https://pan.baidu.com/s/1lRhEIXmHUDvJd8uhuNIVVQ
提取码:hjoq
感谢观看,对你有用的话点个赞吧!有什么疑问私信或评论都可。