3D旋转 + SVG路径动画 + 左右按钮控制

3D旋转 + SVG路径动画 + 左右按钮控制

一、代码示例

<template>
    <div>
        <div class="container">
            <!-- 3D场景 -->
            <div class="scene">
                <div class="carousel" id="carousel">
                    <!-- 菜单项由JavaScript动态生成 -->
                    <div ref="menuItem" v-for="item in menuItems" class="menu-item" @mouseover="fnMouseOver()" @mouseout="fnMouseOut()" @click="fnClickUp(item)">
                        {{ item.title }}
                    </div>
                </div>
            </div>

            <!-- <div class="indicator">
                当前选中: <span id="currentIndex">{{ this.activeIndex }}</span>/<span id="totalItems"> {{  this.menuItems.length }}</span>
            </div> -->

            <!-- 控制按钮 -->
            <div class="controls">
                <button class="control-btn" id="rightBtn" @click="rotateNext">
                    <i class="fas fa-chevron-right"></i> 下一个
                </button>
                <button class="control-btn" id="leftBtn" @click="rotatePrev">
                    <i class="fas fa-chevron-left"></i> 上一个
                </button>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "index",
        data() {
            return {
                menuItems: [
                    { title: "就业完成指标情况分析", description: "描述" },
                    { title: "业务办理情况分析", description: "描述" },
                    { title: "登记失业人员分析", description: "描述" },
                    { title: "城镇新增人员就业分析", description: "描述" },
                    { title: "技能培训分析", description: "描述" },
                    { title: "职资帮扶情况分析", description: "描述" },
                    { title: "基本信息分析", description: "描述" },
                    { title: "农村劳动力转移就业情况", description: "描述" },
                    { title: "城镇就业就业情况", description: "描述" },
                    { title: "就业帮扶工作情况", description: "描述" },
                    { title: "站点招工信息环境", description: "描述" },
                    { title: "联合企业业人员情况", description: "描述" }
                ],
                currentRotation: 0,
                activeIndex: 0,
                rotationStep: 0, // 每个菜单项的角度(30度)
                rotationDirection: 'right', // 旋转方向(right,left)
                timer: null, // 定时器
                isRunning: true, // 控制旋转状态
            }
        },
        mounted() {
            this.initCarousel();
            this.startTimer()
        },
        methods: {
            initCarousel() {
                this.rotationStep = 360 / this.menuItems.length
                // 旋转控制变量
                this.updateMenuPositions();
            },
            updateMenuPositions () {
                const radius = 300; // 旋转半径
                const count = this.menuItems.length;
                this.$refs.menuItem.forEach((item, index) => {
                    // 计算角度(添加偏移量实现旋转)
                    const angle = (index / count) * Math.PI * 2 + this.currentRotation;

                    // 计算3D位置(上下位置调换)
                    const x = Math.sin(angle) * radius + 50;
                    const z = Math.cos(angle) * radius + 50;
                    const y = Math.cos(angle) * 50; // 上下调换后的垂直偏移

                    // 计算与观察者的距离(用于远近效果)
                    const distance = Math.sqrt(x*x + y*y + z*z);
                    const maxDistance = Math.sqrt(radius*radius + radius*radius);

                    // 根据距离计算缩放比例和透明度(近大远小,近实远虚)
                    // const scale = 0.7 + 0.5 * (1 - distance / maxDistance);
                    const scale = 0.7 - 0.5 * (1 - distance / maxDistance);
                    const opacity = 0.5 + 0.5 * (1 - distance / maxDistance);

                    // 应用变换
                    item.style.transform = `
                    translate3d(0, 0, 0)
                    translateX(${x}px)
                    translateY(${y}px)
                    translateZ(${z}px)
                    scale(${scale})
                `;

                    // 应用透明度
                    item.style.opacity = opacity;

                    // 激活状态处理
                    if (index === this.activeIndex) {
                        item.classList.add('active');
                    } else {
                        item.classList.remove('active');
                    }
                })
            },

            // 旋转到指定索引
            rotateToIndex(index) {
                // 计算目标旋转角度(使该索引位于前面)
                const targetRotation = -index * (Math.PI * 2 / this.menuItems.length);

                // 应用旋转
                this.currentRotation = targetRotation;
                this.activeIndex = index;
                this.updateMenuPositions();
            },

            // 旋转到下一个项目
            rotateNext() {
                this.activeIndex = (this.activeIndex + 1) % this.menuItems.length;
                this.rotateToIndex(this.activeIndex);
            },

            // 旋转到上一个项目
            rotatePrev() {
                this.activeIndex = (this.activeIndex - 1 + this.menuItems.length) % this.menuItems.length;
                this.rotateToIndex(this.activeIndex);
            },
            fnMouseOver() {
                this.isRunning = false;
            },
            fnMouseOut() {
                this.isRunning = true;
            },
            fnClickUp (item) {

            },
            startTimer() {
                this.timer = setInterval(() => {
                    if(!this.isRunning) return
                    if (this.rotationDirection === 'right') {
                        this.rotatePrev();
                    } else {
                        this.rotateNext();
                    }
                }, 2000);

            },
            pauseTimer() {
                this.isRunning = false;
                clearInterval(this.timer);
                this.timer = null;
            }
        }
    }
</script>

<style scoped>
    .container {
        position: relative;
        top: 8rem;
        width: 100%;
        max-width: 1200px;
        text-align: center;
        padding: 20px;
        z-index: 10;
    }
    /* 3D场景容器 */
    .scene {
        width: 500px;
        height: 300px;
        margin: 0 auto 50px;
        position: relative;
        transform-style: preserve-3d;
    }

    .carousel {
        width: 100%;
        height: 100%;
        position: relative;
        transform-style: preserve-3d;
        transform: rotateX(-10deg); /* 上下翻转 */
        transition: transform 0.8s cubic-bezier(0.25, 0.1, 0.25, 1);
    }

    /* 菜单项样式 */
    .menu-item {
        position: absolute;
        width: 100px;
        height: 80px;
        background: rgba(255, 255, 255, 0.1);
        border-radius: 15px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        color: #000;
        font-weight: bold;
        box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
        cursor: pointer;
        transition: all 0.5s ease;
        transform-origin: center center;
        transform-style: preserve-3d;
        backdrop-filter: blur(8px);
        border: 1px solid rgba(255, 255, 255, 0.15);
        overflow: hidden;
        text-align: center;
        padding: 15px;
        font-size: 16px;
        line-height: 1.4;
    }

    .menu-item::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 4px;
        background: linear-gradient(90deg, #457b9d, #a8dadc);
    }

    /* 悬停效果 */
    .menu-item:hover {
        background: rgba(168, 218, 220, 0.2);
        box-shadow: 0 8px 25px rgba(69, 123, 157, 0.5);
    }

    /* 选中项效果 */
    .menu-item.active {
        background: rgba(69, 123, 157, 0.3);
        box-shadow: 0 0 25px rgba(69, 123, 157, 0.6);
        border: 1px solid rgba(168, 218, 220, 0.5);
        transform: translateZ(20px) scale(1.05);
    }

    /* 控制按钮容器 */
    .controls {
        display: flex;
        justify-content: center;
        gap: 20px;
        margin-top: 30px;
        flex-wrap: wrap;
    }

    /* 控制按钮样式 */
    .control-btn {
        padding: 14px 30px;
        background: rgba(255, 255, 255, 0.1);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 50px;
        color: #000;
        font-size: 1.05rem;
        cursor: pointer;
        transition: all 0.3s ease;
        backdrop-filter: blur(5px);
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        display: flex;
        align-items: center;
        gap: 10px;
    }

    .control-btn:hover {
        background: rgba(69, 123, 157, 0.25);
        transform: translateY(-3px);
        box-shadow: 0 8px 20px rgba(69, 123, 157, 0.3);
    }

    .control-btn:active {
        transform: translateY(1px);
    }

    /* 选中项信息面板 */
    /* .selected-info {
        background: rgba(255, 255, 255, 0.05);
        border-radius: 20px;
        padding: 25px;
        margin-top: 40px;
        backdrop-filter: blur(8px);
        border: 1px solid rgba(255, 255, 255, 0.1);
        max-width: 800px;
        margin: 50px auto 0;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
        min-height: 180px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }

    .selected-info h2 {
        color: #a8dadc;
        margin-bottom: 20px;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 12px;
        font-size: 1.8rem;
    }

    .selected-info p {
        color: rgba(255, 255, 255, 0.9);
        line-height: 1.7;
        font-size: 1.1rem;
        text-align: center;
    } */

    /* .stats {
        display: flex;
        gap: 30px;
        margin-top: 20px;
        flex-wrap: wrap;
        justify-content: center;
    }

    .stat-item {
        background: rgba(255, 255, 255, 0.07);
        border-radius: 15px;
        padding: 15px 25px;
        min-width: 150px;
        border: 1px solid rgba(168, 218, 220, 0.2);
    }

    .stat-value {
        font-size: 2rem;
        font-weight: bold;
        color: #a8dadc;
        margin-bottom: 5px;
    }

    .stat-label {
        font-size: 0.9rem;
        color: rgba(255, 255, 255, 0.7);
    } */



    /* 响应式设计 */
    /* @media (max-width: 768px) {
        .scene {
            width: 350px;
            height: 350px;
        }

        .menu-item {
            width: 130px;
            height: 100px;
            font-size: 14px;
            padding: 10px;
        }

        h1 {
            font-size: 2.2rem;
        }

        .controls {
            gap: 15px;
        }

        .control-btn {
            padding: 12px 20px;
            font-size: 0.95rem;
        }

        .selected-info h2 {
            font-size: 1.5rem;
        }

        .selected-info p {
            font-size: 1rem;
        }
    } */

    /* 指示器 */
    .indicator {
        margin-top: 20px;
        color: rgba(255, 255, 255, 0.7);
        font-size: 0.9rem;
    }
</style>

 

二、效果图:

 

posted @ 2025-07-14 11:29  慕容冰菡  阅读(25)  评论(0)    收藏  举报