首页/文章列表/文章详情

使用threejs实现3D卡片菜单

编程知识2112024-07-25评论

 

成品效果:

 

 

 

用到的技术:vue2、three.js、gsap.js

template

<template>
  <div></div>
</template>

script

import * as THREE from"three";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";import { CSS3DObject, CSS3DRenderer } from "three/examples/jsm/renderers/CSS3DRenderer.js";import gsap from "gsap";const httpMatcher = /http|https/;exportdefault { name: "3DMenu", components: {}, data() { return { app: null, el: null, mesh: null, camera: null, scene: null, renderer: null, labelRenderer: null, controls: null, menuData: [ { id: "1", parentId: "1537645492375449602", name: "用户中心", description: null, appKey: "xjt_user", appHomePage: "/auth-ui/", }, { id: "1534774879700992002", parentId: "1537645492375449602", name: "人资系统", description: null, appKey: "xjt_hr", appHomePage: "/hr-ui/", }, { id: "1536947570488430593", parentId: "1537645492375449602", name: "合同系统", description: null, appKey: "xjt_contract", appHomePage: "/contract-ui/", }, { id: "1537733169730351105", parentId: "1537645492375449602", name: "OA系统", description: null, appKey: "xjt_oa", appHomePage: "/oa-ui/", }, { id: "1551507637786374145", parentId: "1537645492375449602", name: "费报系统", description: null, appKey: "xjt_fb", appHomePage: "/feibao-ui/", }, { id: "1613789365929680897", parentId: "1537645492375449602", name: "考试系统", description: null, appKey: "xjt_exam", appHomePage: "/exam-ui/", }, { id: "1615265465629380610", parentId: "1537645492375449602", name: "培训系统", description: null, appKey: "xjt_px", appHomePage: "/px-ui/", }, { id: "1669546339670454274", parentId: "1537645492375449602", name: "会议系统", description: null, appKey: "xjt_cloud_meeting", appHomePage: "/cloud-meeting-ui/", }, { id: "1674596267673264130", parentId: "1537645492375449602", name: "资产系统", description: null, appKey: "xjt_property", appHomePage: "/property-ui/", }, ], radius: 400, objects: [], spheres: [], //用来存放目标对象的位置isAnimationPaused:false, }; }, mounted() { this.initZThree(); window.addEventListener("resize",this.handleResize); }, beforeDestroy() { window.removeEventListener("resize",this.handleResize);this.destroyThree(); }, methods: { initZThree() { this.el = document.getElementById("box"); const { offsetWidth, offsetHeight } =this.el;this.initScene();this.initCamera(offsetWidth, offsetHeight); this.initRenderer(offsetWidth, offsetHeight); this.initControl();this.initMenu(); }, initScene() { //渲染场景this.scene = new THREE.Scene(); }, initCamera(offsetWidth, offsetHeight) { //创建相机this.camera = newTHREE.PerspectiveCamera(50, offsetWidth /offsetHeight,1,20000);this.camera.position.set(-1265, 798, -105); //设置相机位置this.camera.lookAt(0, 0, 0); //设置相机看先中心点 }, initRenderer(offsetWidth, offsetHeight) { //创建渲染器this.renderer = new THREE.WebGLRenderer({ antialias: true,//true/false表示是否开启反锯齿alpha:true,// true/false 表示是否可以设置背景色透明});this.renderer.setSize(offsetWidth, offsetHeight); //设置渲染区域宽高this.renderer.shadowMap.enabled = true;//允许渲染器产生阴影贴图this.renderer.setPixelRatio(window.devicePixelRatio);this.renderer.setClearColor(0x01dcc9, 0); //设置背景颜色this.el.append(this.renderer.domElement);//网页标签this.labelRenderer = newCSS3DRenderer();this.labelRenderer.domElement.style.zIndex = 2;this.labelRenderer.domElement.style.position ="absolute";this.labelRenderer.domElement.style.top ="0px";this.labelRenderer.domElement.style.left ="0px";this.labelRenderer.domElement.style.pointerEvents ="none"; //避免HTML标签遮挡三维场景的鼠标事件this.labelRenderer.setSize(offsetWidth, offsetHeight); this.labelRenderer.domElement.addEventListener("mousemove",this.handleMousemove);this.labelRenderer.domElement.addEventListener("mouseout",this.handleMouseout);this.el.appendChild(this.labelRenderer.domElement); }, initControl() { //初始化控制器 let controls = newOrbitControls(this.camera,this.renderer.domElement);// controls.autoRotate = true; //为true时,相机自动围绕目标旋转,但必须在animation循环中调用update() controls.enableDamping = true;//设置带阻尼的惯性 controls.dampingFactor = 0.05; //设置阻尼的系数//避免鼠标滚轮放大缩小 controls.minDistance = 1500; controls.maxDistance = 1500;this.controls =controls;this.controls.update(); }, initMenu() { this.objects =[];this.spheres =[];this.menuData.forEach((item, index) => { const cardLabel =this.addCss3dLabel(item, index + 1); cardLabel.element.addEventListener("click",this.handleClick);this.objects.push(cardLabel);this.scene.add(cardLabel); }); const vector =new THREE.Vector3(20, 20, 20);for (let i = 0, l = this.objects.length; i < l; i++) { const phi = (i / l) * 2 * Math.PI; //分配每个对象在圆上的角度 const object = new THREE.Object3D(); object.position.x =this.radius * Math.cos(phi); object.position.y = 0; object.position.z =this.radius *Math.sin(phi);//设置对象朝向圆心 vector.x = object.position.x; vector.y = object.position.y; vector.z = object.position.z; object.lookAt(vector); this.spheres.push(object); } this.transform();this.renderFun();//渲染 }, addCss3dLabel(item = {}, index) { const element = document.createElement("div"); element.className = `sys-item-li sys-item-${index}`; element.innerHTML = `<div><div data-url="${item.appHomePage}"><div></div><div>${item.name}</div><div>点击进入<i></i></div></div></div>`; let textLabel =new CSS3DObject(element); textLabel.name = item.name; textLabel.userData = item; const position = Math.random() * this.radius + this.radius; textLabel.position.set(position, position, position); return textLabel; }, renderFun() { this.objects.forEach((object) => { object.lookAt(this.camera.position); }); if(!this.isAnimationPaused) { this.scene.rotation.y -= 0.005; //旋转速度}this.renderer.render(this.scene,this.camera);this.labelRenderer.render(this.scene,this.camera); requestAnimationFrame(this.renderFun); }, transform(duration = 2) { for(var i = 0; i < this.objects.length; i++) { let object =this.objects[i]; let target =this.spheres[i]; gsap.to(object.position, { x: target.position.x, y: target.position.y, z: target.position.z, duration: Math.random() * duration + duration, ease: "Linear.inOut", }); gsap.to(object.rotation, { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z, duration: Math.random() * duration + duration, ease: "Linear.inOut", }); } }, handleResize() { this.camera.aspect = this.el.offsetWidth / this.el.offsetHeight;this.camera.updateProjectionMatrix();this.renderer.setSize(this.el.offsetWidth,this.el.offsetHeight);this.labelRenderer.setSize(this.el.offsetWidth,this.el.offsetHeight); }, handleMousemove() { this.isAnimationPaused = true;//暂停动画 }, handleMouseout() { this.isAnimationPaused = false;//恢复动画 }, handleClick(e) { const { url } = e.target.dataset; console.log("url", url); if (httpMatcher.test(url)) { window.location.href = url; } else { window.location.href = `${window.location.origin}${url}`; } }, destroyThree() { this.scene.traverse((child) =>{if (child.material) { child.material.dispose(); } if (child.geometry) { child.geometry.dispose(); } child =null; }); this.renderer.forceContextLoss();this.renderer.dispose();this.scene.clear(); }, },};

css

.container{width:100%;height:100%;overflow:hidden;background-color:#f2f6fe;background-image:url(~@/assets/images/subsystem/switch-system-bg.jpg);background-size:cover;background-repeat:no-repeat; ::v-deep.sys-item { opacity: 1;width:24vh;height:24vh;text-align:center;color:#fff;border: 1px solid rgba(255, 255, 255, 0.3);border-radius:16px;overflow:hidden;color:#3768f5;transform:rotate(45deg);cursor:pointer;&::before { content:"";position:absolute;width:100%;height:100%;margin-left:-50%;z-index:1;box-sizing:border-box;border-radius:16px;border: 2px solid rgba(255, 255, 255, 0.5);background: linear-gradient(90deg, #f2efff 0%, #fff 100%);transition: all 0.25s ease; }&:hover{transform: rotate(45deg) scale(1.07);box-shadow: 0 2px 24px 16px rgba(0, 142, 255, 0.08);background: linear-gradient(135deg, #fff 0%, #cbe8ff 100%);&::before { opacity: 1;border: 2px solid #4a93ff;box-shadow: 0 2px 24px 12px rgba(0, 142, 255, 0.08);background: linear-gradient(135deg, #fff 0%, #cbe8ff 100%); }.sys-btn{color:#fff;background: rgba(55, 102, 245, 0.8); } } .sys-content {position:relative;width:100%;height:100%;transform:rotate(-45deg);z-index:9; }.sys-bg{width:55%;height:55%;margin:auto;pointer-events:none;background-size:cover;background-repeat:no-repeat;background-image:url(~@/assets/images/subsystem/xjt_contract.png); &.xjt_user { background-image:url(~@/assets/images/subsystem/xjt_user.png); }&.xjt_hr{background-image:url(~@/assets/images/subsystem/xjt_hr.png); } &.xjt_fb, &.expense {background-image:url(~@/assets/images/subsystem/xjt_fb.png); }&.xjt_budget{background-image:url(~@/assets/images/subsystem/xjt_budget.png); }&.xjt_px{background-image:url(~@/assets/images/subsystem/xjt_px.png); }&.xjt_contract{background-image:url(~@/assets/images/subsystem/xjt_contract.png); }&.xjt_oa{background-image:url(~@/assets/images/subsystem/xjt_oa.png); }&.xjt_exam{background-image:url(~@/assets/images/subsystem/xjt_exam.png); }&.xjt_cloud_meeting{background-image:url(~@/assets/images/subsystem/xjt_cloud_meeting.png); } } .sys-name {font-size:2.7vh;font-weight:600;pointer-events:none; }.sys-btn{display:inline-block;height:4vh;padding: 0 1.2vh;margin-top:1vh;line-height:4vh;font-size:1.8vh;font-weight:500;border-radius:2vh;transition: all 0.2s ease;cursor:pointer;pointer-events:none; .el-icon-arrow-right { vertical-align:middle;margin-top:-1px; } } }}

 

博客园

这个人很懒...

用户评论 (0)

发表评论

captcha