本代码的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;// 创建全局uniformthis.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.5let 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 = falsestore.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 {// 没有tokenif (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】星辰宇宙教学 或许这就是代码的魅力 (附源码)