296 lines
6.2 KiB
Vue
296 lines
6.2 KiB
Vue
<template>
|
|
<view class="bottom-nav-container">
|
|
<!-- 导航底座 -->
|
|
<view class="base-wrapper" :class="{ 'expanded': isExpanded }">
|
|
<image
|
|
class="base-icon"
|
|
src="/static/icon/nav-base.png"
|
|
mode="aspectFit"
|
|
></image>
|
|
</view>
|
|
|
|
<!-- 小怪兽图标 -->
|
|
<view class="monster-wrapper" :class="{ 'expanded': isExpanded }">
|
|
<image
|
|
class="monster-icon"
|
|
src="/static/icon/character.png"
|
|
mode="aspectFit"
|
|
@click="toggleExpand"
|
|
></image>
|
|
</view>
|
|
|
|
<!-- 导航图标 -->
|
|
<view
|
|
v-for="(item, index) in navItems"
|
|
:key="index"
|
|
class="nav-icon-wrapper"
|
|
:class="{ 'expanded': isExpanded, 'active': activeTab === index }"
|
|
:style="getNavIconStyle(index)"
|
|
@click="handleNavClick(index)"
|
|
>
|
|
<image
|
|
class="nav-icon"
|
|
:src="item.icon"
|
|
mode="aspectFit"
|
|
></image>
|
|
<text class="nav-label">{{ item.name }}</text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed } from 'vue';
|
|
|
|
// 定义 props
|
|
const props = defineProps({
|
|
activeTab: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
isExpanded: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
});
|
|
|
|
// 定义 emits
|
|
const emit = defineEmits(['update:activeTab', 'update:isExpanded']);
|
|
|
|
// 导航项配置
|
|
const navItems = [
|
|
{
|
|
name: '广场',
|
|
icon: '/static/icon/square.png',
|
|
angle: 122 // 左上方
|
|
},
|
|
{
|
|
name: '星册',
|
|
icon: '/static/icon/starbook.png',
|
|
angle: 107 // 右上方
|
|
},
|
|
{
|
|
name: '铸爱',
|
|
icon: '/static/icon/castlove.png',
|
|
angle: 90.01 // 正上方
|
|
},
|
|
{
|
|
name: '星城',
|
|
icon: '/static/icon/dressup.png',
|
|
angle: 73 // 右方
|
|
},
|
|
{
|
|
name: '好友',
|
|
icon: '/static/icon/friends.png',
|
|
angle: 57 // 右下方
|
|
}
|
|
];
|
|
|
|
// 扇形半径
|
|
const radius = 580; // rpx
|
|
|
|
// 计算每个导航图标的位置
|
|
const getNavIconStyle = (index) => {
|
|
if (!props.isExpanded) {
|
|
// 未展开时,所有图标都在中心位置,透明且缩小
|
|
return {
|
|
'--x': '0rpx',
|
|
'--y': '0rpx',
|
|
'--delay': '0s'
|
|
};
|
|
}
|
|
|
|
const item = navItems[index];
|
|
const angle = item.angle;
|
|
const radian = angle * Math.PI / 180;
|
|
const x = radius * Math.cos(radian);
|
|
const y = -radius * Math.sin(radian);
|
|
|
|
// 添加延迟,形成序列动画效果
|
|
const delay = index * 0.03;
|
|
|
|
return {
|
|
'--x': `${x}rpx`,
|
|
'--y': `${y}rpx`,
|
|
'--delay': `${delay}s`
|
|
};
|
|
};
|
|
|
|
// 切换展开/收起状态
|
|
const toggleExpand = () => {
|
|
emit('update:isExpanded', !props.isExpanded);
|
|
};
|
|
|
|
// 处理导航点击
|
|
const handleNavClick = (index) => {
|
|
// 触发事件,通知父组件切换 tab
|
|
emit('update:activeTab', index);
|
|
// 收起导航栏
|
|
emit('update:isExpanded', false);
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.bottom-nav-container {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
width: 100%;
|
|
height: 300rpx;
|
|
pointer-events: none;
|
|
z-index: 1000;
|
|
}
|
|
|
|
/* 导航底座 */
|
|
.base-wrapper {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(1500rpx);
|
|
width: 1200rpx;
|
|
height: 1200rpx;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
transition: transform 0.3s cubic-bezier(0.34, 1.26, 0.64, 1);
|
|
opacity: 0;
|
|
}
|
|
|
|
.base-wrapper.expanded {
|
|
transform: translateX(-50%) translateY(900rpx);
|
|
opacity: 1;
|
|
}
|
|
|
|
.base-icon {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* 小怪兽图标容器 */
|
|
.monster-wrapper {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(60%);
|
|
width: 300rpx;
|
|
height: 300rpx;
|
|
pointer-events: auto;
|
|
z-index: 3;
|
|
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
.monster-wrapper.expanded {
|
|
transform: translateX(-50%) translateY(90rpx) scale(1.15);
|
|
}
|
|
|
|
.monster-icon {
|
|
width: 100%;
|
|
height: 100%;
|
|
filter: drop-shadow(0 8rpx 24rpx rgba(255, 107, 157, 0.5))
|
|
drop-shadow(0 0 40rpx rgba(255, 140, 180, 0.3));
|
|
transition: filter 0.3s ease, transform 0.3s ease;
|
|
animation: breathe 2s ease-in-out infinite;
|
|
}
|
|
|
|
.monster-wrapper.expanded .monster-icon {
|
|
filter: drop-shadow(0 16rpx 40rpx rgba(255, 107, 157, 0.7))
|
|
drop-shadow(0 0 60rpx rgba(255, 140, 180, 0.5));
|
|
animation: none;
|
|
}
|
|
|
|
/* 小怪兽呼吸动画 */
|
|
@keyframes breathe {
|
|
0%, 100% {
|
|
transform: scale(1.00);
|
|
}
|
|
50% {
|
|
transform: scale(1.02);
|
|
}
|
|
}
|
|
|
|
/* 导航图标容器 */
|
|
.nav-icon-wrapper {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
width: 140rpx;
|
|
height: 180rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transform: translate(-50%, 0) scale(0);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
z-index: 5;
|
|
transition: transform 0.35s cubic-bezier(0.34, 1.36, 0.64, 1),
|
|
opacity 0.3s ease-out;
|
|
transition-delay: var(--delay);
|
|
}
|
|
|
|
.nav-icon-wrapper.expanded {
|
|
transform: translate(calc(-50% + var(--x)), calc(200px + var(--y))) scale(1);
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.nav-icon {
|
|
width: 100rpx;
|
|
height: 100rpx;
|
|
margin-bottom: 8rpx;
|
|
transition: transform 0.3s ease, filter 0.3s ease;
|
|
filter: drop-shadow(0 6rpx 16rpx rgba(0, 0, 0, 0.4))
|
|
drop-shadow(0 2rpx 8rpx rgba(0, 0, 0, 0.2));
|
|
position: relative;
|
|
}
|
|
|
|
/* 底部倒影效果 */
|
|
.nav-icon::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -100%;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: inherit;
|
|
opacity: 0.2;
|
|
transform: scaleY(-1);
|
|
filter: blur(4rpx);
|
|
mask-image: linear-gradient(to bottom, rgba(0,0,0,0.3) 0%, transparent 60%);
|
|
-webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,0.3) 0%, transparent 60%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.nav-icon-wrapper.active .nav-icon {
|
|
transform: scale(1.25);
|
|
filter: drop-shadow(0 8rpx 24rpx rgba(255, 107, 157, 0.7))
|
|
drop-shadow(0 4rpx 16rpx rgba(255, 140, 66, 0.5))
|
|
drop-shadow(0 0 32rpx rgba(255, 107, 157, 0.4));
|
|
}
|
|
|
|
.nav-icon-wrapper:active .nav-icon {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.nav-icon-wrapper.active:active .nav-icon {
|
|
transform: scale(1.15);
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 24rpx;
|
|
color: rgba(255, 255, 255, 0.98);
|
|
font-weight: 500;
|
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.4),
|
|
0 1rpx 4rpx rgba(0, 0, 0, 0.3);
|
|
white-space: nowrap;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.nav-icon-wrapper.active .nav-label {
|
|
color: #e6e6e6;
|
|
font-weight: bold;
|
|
text-shadow: 0 2rpx 12rpx rgba(255, 107, 157, 0.6),
|
|
0 1rpx 6rpx rgba(0, 0, 0, 0.4);
|
|
transform: scale(1.05);
|
|
}
|
|
</style>
|