成品效果:

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

template

<template>
<div id="box" class="container"></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/;
export default {
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 = new THREE.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 = new CSS3DRenderer();
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 = new OrbitControls(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 class="sys-item"><div class="sys-content" data-url="${item.appHomePage}"><div class="sys-bg ${item.appKey}"></div><div class="sys-name">${item.name}</div><div class="sys-btn">点击进入<i class="el-icon-arrow-right"></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;
}
}
}
}

使用threejs实现3D卡片菜单的更多相关文章

  1. 转 threejs中3D视野的缩放实现

    Threejs基础部分学习知道透视相机new THREE.PerspectiveCamera(fov, aspect , near,far)中. fov视野角(拍摄距离)越大,场景中的物体越小.fov ...

  2. 3D旋转菜单

    今天来个3D旋转菜单,是纯css3实现的,主要用到transform,transition,backface-visibility. 主要是transform这个变换,它是今天猪脚. transfor ...

  3. Threejs 开发3D地图实践总结【转】

    Threejs 开发3D地图实践总结   前段时间连续上了一个月班,加班加点完成了一个3D攻坚项目.也算是由传统web转型到webgl图形学开发中,坑不少,做了一下总结分享. 1.法向量问题 法线是垂 ...

  4. threejs和3d各种效果的学习

    写给即将开始threejs学习的自己,各种尝试,各种记忆.不要怕,灰色的年华终会过去. 一个技术学习的快慢,以及你的深刻程度,还有你的以后遇到这个东西的时候的反应速度,很大程度上,取决于你的博客的深刻 ...

  5. 纯css3响应式3d翻转菜单

    前端开发whqet,csdn,王海庆,whqet,前端开发专家 周末快乐哈,今天来看一个纯CSS3实现的3d翻转菜单.3d响应式菜单,希望对大家有所帮助. 在线赞赏效果.在线编辑代码,或者下载收藏. ...

  6. 3D立体菜单导航

    今天在微博里面看到别人分享的一个立体效果,我觉得挺好的,就拿下来自己存着,万一以后用到. 效果如下: index.html <!DOCTYPE html> <html > &l ...

  7. 教你如何利用threejs对3D模型皮肤进行DIY

    一步一步教你如何利用threejs加载gltf模型来实现DIY换肤功能. 模型准备 模型制作 模型可以通过网上下载,也可以自己通过c4d.maya.blender等模型制作软件得到.这里就不叙述有关模 ...

  8. 用threejs 实现3D物体在浏览器展示

    用threejs 实现3D物体在浏览器展示,通过鼠标平移,缩放,键盘箭头按钮左右移动等功能展示. <!DOCTYPE html> <html> <head> < ...

  9. Vue.js 实现的 3D Tab菜单

    今天给大家带来一款基于VueJS的3D Tab菜单,它跟我们之前分享的许多CSS3 Tab菜单不同的是,它可以随着鼠标移动呈现出3D立体的视觉效果,每个tab页面还可以通过CSS自定义封面照片.它的核 ...

  10. 【译】仿Taasky的3D翻转菜单动画实现

    最终效果 最终效果 开始 首先下载并打开一个事先搭好架子的Demo,然后来分析一下.这个Demo包含一个主页和详情页,其中MenuViewController继承自UITableViewControl ...

随机推荐

  1. 分布式缓存NewLife.Redis

    NewLife.Redis 是一个Redis客户端组件,以高性能处理大数据实时计算为目标. Redis协议基础实现位于Redis/RedisClient,FullRedis为扩展实现,主要增加列表结构 ...

  2. Canny边缘检测实现(Opencv C++)

    Canny边缘检测是Canny在1986年提出来的,目前仍是图像边缘检测算法中最经典.先进的算法之一.canny方法基于如下三个基本目标: 1. 低错误率:所有边缘都应被找到,并且不应有虚假响应. 2 ...

  3. 机器学习策略篇:详解如何改善你的模型的表现(Improving your model performance)

    如何改善模型的表现 学过正交化,如何设立开发集和测试集,用人类水平错误率来估计贝叶斯错误率以及如何估计可避免偏差和方差.现在把它们全部组合起来写成一套指导方针,如何提高学习算法性能的指导方针. 所以想 ...

  4. AGC043

    AGC043 A.Range Flip Find Route 简单DP B.123 Triangle 推性质. 利用模运算将减法变成加法(在绝对值0/1的情况下). Giant Graph 类似于博弈 ...

  5. 算法学习笔记(15): Trie(字典树)

    Trie树 Trie(字典树)是一种用于实现字符串检索的多叉树. Trie的每一个节点都可以通过 c 转移到下一层的一个节点. 我们可以看作可以通过某个字符转移到下一个字符串状态,直到转移到最终态为止 ...

  6. kettle从入门到精通 第五十八课 ETL之kettle HTTP post使用教程

    1.今天群里有位朋友问我有没有关于调用http接口的kettle 示例,我下意识的去翻我的公众号推文,愣是没找到.果断开始撸. 2.本次演示流程通过调用接口[网易云音乐随机歌曲],然后解析返回的数据, ...

  7. 微信刷脸SDK获取sub_openid

    当调用SDK中 获取用户信息(getWxpayfaceUserInfo) /人脸支付凭证(getWxpayfaceCode) 方法获取 sub_openid 时,除了SDK自身要传入sub_appid ...

  8. Scrapy框架(六)--图片数据抓取

    基于文件下载的管道类 在scrapy中我们之前爬取的都是基于字符串类型的数据,那么要是基于图片数据的爬取,那又该如何呢? 其实在scrapy中已经为我们封装好了一个专门基于图片请求和持久化存储的管道类 ...

  9. 开源一款功能强大的 .NET 消息队列通讯模型框架 Maomi.MQ

    目录 文档说明 导读 快速开始 消息发布者 IMessagePublisher 连接池 消息过期 事务 发送方确认模式 独占模式 消费者 消费者模式 事件模式 分组 消费者模式 消费.重试和补偿 消费 ...

  10. SQLite vs MySQL vs PostgreSQL对比总结

    开发业务系统时,是绕不开RDBMS(关系型数据库)的.虽然现在诞生了各种NoSQL的数据库,RDBMS在业务系统中的严谨和优势依然无法取代. 近几年大大小小的项目中,常用的三种RDBMS(SQLite ...