本代码的html版本,来源自“山羊の前端小窝”作者,我对此进行了vue版本转换以及相关应用。特此与大家一起分享~
1、直接上效果图:
带文字版:文字呼吸式缩放。
纯净版:
默认展示效果:
缩放与旋转后:
2、代码
话不多说,直接上代码
<template>
<div id="threejs-scene">
<div class="header-text" ref="headerText">万千星辰,<span @click="redirectToPage" style="font-weight: bold">我</span>,会归于何方?</div>
</div>
</template>
<!--//我在上方做了个跳转,这是在若依中设置的,不需要的直接删除即可-->
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
//需要npm install相关插件,一般会有提示,请直接安装
export default {
mounted() {
console.clear();
// 创建场景
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x160016);
// 创建相机
this.camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
1,
1000
);
this.camera.position.set(0, 4, 21);
// 创建渲染器
this.renderer = new THREE.WebGLRenderer();
// 设置渲染器大小
this.renderer.setSize(window.innerWidth, window.innerHeight);
// 把渲染器加入到页面中
this.$el.appendChild(this.renderer.domElement);
// 监听窗口大小变化事件
window.addEventListener("resize", this.handleWindowResize);
// 创建控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
// 开启阻尼效果
this.controls.enableDamping = true;
// 禁用面板
this.controls.enablePan = false;
// 创建全局uniform
this.gu = {
time: { value: 0 },
};
// 创建时钟
this.clock = new THREE.Clock();
// 生成点云数据
this.generatePointCloud();
// 设置渲染循环
this.renderer.setAnimationLoop(this.animate);
},
methods: {
redirectToPage() {
// 页面跳转
this.$router.push({path:"/loginForUser"});
},
handleWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
},
generatePointCloud() {
let pts = [];
let sizes = [];
let shift = [];
// 创建移动函数
const pushShift = () => {
shift.push(
Math.random() * Math.PI,
Math.random() * Math.PI * 2,
(Math.random() * 0.9 + 0.1) * Math.PI * 0.1,
Math.random() * 0.9 + 0.1
);
};
// 创建点的顶点数组(中间的球体)
for (let i = 0; i < 50000; i++) {
sizes.push(Math.random() * 1.5 + 0.5);
pushShift();
pts.push(
new THREE.Vector3()
.randomDirection()
.multiplyScalar(Math.random() * 0.5 + 9.5)
);
}
// 添加更多的点(旁边围绕的)
for (let i = 0; i < 100000; i++) {
let r = 10,
R = 40;
let rand = Math.pow(Math.random(), 1.5);
let radius = Math.sqrt(R * R * rand + (1 - rand) * r * r);
pts.push(
new THREE.Vector3().setFromCylindricalCoords(
radius,
Math.random() * 2 * Math.PI,
(Math.random() - 0.5) * 2
)
);
sizes.push(Math.random() * 1.5 + 0.5);
pushShift();
}
// 创建点云几何体
let geometry = new THREE.BufferGeometry().setFromPoints(pts);
geometry.setAttribute("sizes", new THREE.Float32BufferAttribute(sizes, 1));
geometry.setAttribute("shift", new THREE.Float32BufferAttribute(shift, 4));
// 创建点云材质
let material = new THREE.PointsMaterial({
size: 0.125,
transparent: true,
depthTest: false,
blending: THREE.AdditiveBlending,
onBeforeCompile: (shader) => {
shader.uniforms.time = this.gu.time;
shader.vertexShader = `
uniform float time;
attribute float sizes;
attribute vec4 shift;
varying vec3 vColor;
${shader.vertexShader}
`
.replace(`gl_PointSize = size;`, `gl_PointSize = size * sizes;`)
.replace(
`#include <color_vertex>`,
`#include <color_vertex>
float d = length(abs(position)/vec3(40.,10.,40));
d=clamp(d,0.,1.);
vColor = mix(vec3(220., 84., 190.),vec3(67., 11., 245.),d)/255.;`
)
//上文vColor为调整颜色的东西,可自定义!
.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
float t = time;
float moveT = mod(shift.x + shift.z * t,PI2);
float moveS = mod(shift.y + shift.z * t,PI2);
transformed += vec3(cos(moveS) * sin(moveT),cos(moveT),sin(moveS)*sin(moveT)) * shift.w;
`
);
shader.fragmentShader = `
varying vec3 vColor;
${shader.fragmentShader}
`
.replace(
`#include <clipping_planes_fragment>`,
`#include <clipping_planes_fragment>
float d = length(gl_PointCoord.xy - 0.5);
`
)
.replace(
`vec4 diffuseColor = vec4( diffuse, opacity );`,
`vec4 diffuseColor = vec4(vColor, smoothstep(0.5, 0.1, length(gl_PointCoord.xy - 0.5))/* * 0.5+0.5*/);`
);
},
});
// 创建点云对象并添加到场景中
this.points = new THREE.Points(geometry, material);
this.points.rotation.order = "ZYX";
this.points.rotation.z = 0.2;
this.scene.add(this.points);
},
animate() {
this.controls.update();
// 获取时钟对象(clock)的已经流逝的时间(t)并将他乘0.5
let t = this.clock.getElapsedTime() * 0.5;
this.gu.time.value = t * Math.PI;
this.points.rotation.y = t * 0.05;
this.renderer.render(this.scene, this.camera);
// 更新文字的缩放比例
//如果不想要文字,请注释掉文字所在的div,以及下面两行:
let scale = 1 + 0.2 * Math.sin(t);
this.$refs.headerText.style.transform = `scale(${scale})`;
},
},
beforeDestroy() {
window.removeEventListener("resize", this.handleWindowResize);
this.renderer.setAnimationLoop(null);
},
};
</script>
<style scoped>
.header-text {
position: absolute;
top: 45%;
left: 35%;
transform: translate(-50%, -50%);
font-size: 50px;
font-weight: bold;
color: #f8df70;
opacity: 90%;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
z-index: 10;
font-family: 'Comic Sans MS', 'Comic Sans', cursive;
text-align: center;
cursor: pointer; /* 让文本在鼠标悬停时显示光标,表明可以点击 */
}
#threejs-scene {
overflow: hidden;
margin: 0;
}
</style>
3、在若依登录前的应用:
当前我实现的效果为:前端页面运行后,直接跳转到星辰页,再靠点击触发登录跳转。
(1)创建页面
复制原有的login.vue页面,将副本命名为:loginForUser用于放原有的登录页面,原有页面替换为上文代码👆
(2)找到router路由位置:添加路由
{
path: '/loginForUser',
component: () => import('@/views/loginForUser'),
hidden: true
},
{
path: '',
component: Layout,
redirect: 'loginForUser',
children: [
{
path: 'loginForUser',
component: () => import('@/views/loginForUser'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
//请将上文loginForUser,替换为你的当前登录页面的名称
注意,做到这一步,一般还是无法跳转的,需要下一步修改:
(3)找到permission.js页面,修改访问白名单,实现跳转逻辑。
permission.js代码参考:
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/loginForUser','/register']
//添加了:'/loginForUser',
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login'|| to.path === '/loginForUser') {
//添加了: to.path === '/loginForUser'
next({ path: '/' })
NProgress.done()
} else if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
(4)星辰页面(login.vue)加入路由跳转:
<template>
<div id="threejs-scene">
<div class="header-text" ref="headerText">万千星辰,<span @click="redirectToPage"
style="font-weight: bold">我</span>,会归于何方?</div>
</div>
</template>
<!--页面中,引入点击事件:@click="redirectToPage"-->
<script>
//……其它代码
methods: {
redirectToPage() {
this.$router.push({path:"/loginForUser"});
// path就是你在router文件中写的,我的为 /loginForUser
},
}
</script>
最后贴一个原作者的视频地址:希望大家也可以支持一下原作者~
【JS】星辰宇宙教学 或许这就是代码的魅力 (附源码)