224 lines
5.1 KiB
Vue
224 lines
5.1 KiB
Vue
<template>
|
||
<view class="laser-variant-pyramid">
|
||
<view
|
||
v-for="(image, index) in paths"
|
||
:key="index"
|
||
class="card-item"
|
||
:class="{ 'card-selected': selectedIndex === index }"
|
||
:style="getCardStyle(index)"
|
||
@tap="onSelect(index)"
|
||
>
|
||
<view class="card-frame">
|
||
<image class="card-image" :src="typeof image === 'object' ? image.url : image" mode="aspectFill" />
|
||
<view class="card-shimmer" :class="{ 'card-shimmer-fast': selectedIndex === index }" />
|
||
<view class="card-rainbow-edge" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
|
||
const props = defineProps({
|
||
paths: {
|
||
type: Array,
|
||
default: () => [],
|
||
},
|
||
selectedIndex: {
|
||
type: Number,
|
||
default: -1,
|
||
},
|
||
/** 礼盒是否已打开(控制卡片错峰入场) */
|
||
opened: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
})
|
||
|
||
const emit = defineEmits(['select'])
|
||
|
||
const onSelect = (index) => {
|
||
emit('select', index)
|
||
}
|
||
|
||
// 复用 generation-result 中「金字塔」视觉参数(rpx)
|
||
const getCardStyle = (index) => {
|
||
// 顶部:1张大(z=30) | 中间:2张中 | 底部:2张小
|
||
const allPositions = [
|
||
{ left: 40, top: 624, rotate: '-5deg', scale: 0.72, zIndex: 10 }, // 0
|
||
{ left: 130, top: 580, rotate: '-8deg', scale: 0.82, zIndex: 20 }, // 1
|
||
{ left: 275, top: 500, rotate: '0deg', scale: 1.15, zIndex: 30 }, // 2
|
||
{ left: 420, top: 580, rotate: '8deg', scale: 0.82, zIndex: 20 }, // 3
|
||
{ left: 510, top: 624, rotate: '5deg', scale: 0.72, zIndex: 10 }, // 4
|
||
]
|
||
|
||
let posIndex
|
||
if (props.selectedIndex === -1) {
|
||
posIndex = index
|
||
} else {
|
||
if (index === props.selectedIndex) {
|
||
posIndex = 2
|
||
} else {
|
||
const relativePos = (index - props.selectedIndex + 5) % 5
|
||
if (relativePos === 1) posIndex = 3
|
||
else if (relativePos === 2) posIndex = 4
|
||
else if (relativePos === 3) posIndex = 0
|
||
else if (relativePos === 4) posIndex = 1
|
||
else posIndex = 2
|
||
}
|
||
}
|
||
|
||
const pos = allPositions[posIndex]
|
||
// 错峰扫光延迟:每张卡 1.6s 错开(更慢,更克制)
|
||
const shimmerDelay = `${(index * 1.6).toFixed(2)}s`
|
||
// 入场延迟:礼盒打开后从中间向两侧展开
|
||
const enterDelay = props.opened ? `${(Math.abs(index - 2) * 0.08).toFixed(2)}s` : '0s'
|
||
|
||
if (props.selectedIndex === index) {
|
||
return {
|
||
left: `${pos.left}rpx`,
|
||
top: `${pos.top}rpx`,
|
||
transform: `scale(${pos.scale * 1.15})`,
|
||
filter: 'brightness(1.05) drop-shadow(0 0 30rpx rgba(160, 200, 220, 0.45))',
|
||
zIndex: 100,
|
||
'--shimmer-delay': shimmerDelay,
|
||
'animation-delay': enterDelay,
|
||
}
|
||
}
|
||
|
||
return {
|
||
left: `${pos.left}rpx`,
|
||
top: `${pos.top}rpx`,
|
||
transform: `scale(${pos.scale})`,
|
||
zIndex: pos.zIndex,
|
||
'--shimmer-delay': shimmerDelay,
|
||
'animation-delay': enterDelay,
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.laser-variant-pyramid {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 20;
|
||
}
|
||
|
||
.card-item {
|
||
position: absolute;
|
||
width: 200rpx;
|
||
height: 260rpx;
|
||
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||
left 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||
top 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||
filter 0.3s ease;
|
||
filter: drop-shadow(0 15rpx 40rpx rgba(0, 0, 0, 0.5));
|
||
animation: card-fade-in 0.55s ease-out backwards;
|
||
}
|
||
|
||
.card-frame {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(
|
||
135deg,
|
||
rgba(180, 220, 255, 0.95) 0%,
|
||
rgba(200, 230, 255, 0.95) 50%,
|
||
rgba(220, 240, 255, 0.95) 100%
|
||
);
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
border: 5rpx solid rgba(255, 255, 255, 0.9);
|
||
padding: 10rpx;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
}
|
||
|
||
.card-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 14rpx;
|
||
display: block;
|
||
}
|
||
|
||
/* 斜向白光扫过 — 暗示「镭射膜」核心视觉(极克制)*/
|
||
.card-shimmer {
|
||
position: absolute;
|
||
inset: 10rpx;
|
||
border-radius: 14rpx;
|
||
background: linear-gradient(
|
||
115deg,
|
||
transparent 32%,
|
||
rgba(255, 255, 255, 0.18) 46%,
|
||
rgba(255, 255, 255, 0.32) 50%,
|
||
rgba(255, 255, 255, 0.18) 54%,
|
||
transparent 68%
|
||
);
|
||
background-size: 220% 220%;
|
||
background-position: -120% -120%;
|
||
animation: card-shimmer-sweep 8s ease-in-out infinite;
|
||
animation-delay: var(--shimmer-delay, 0s);
|
||
mix-blend-mode: screen;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.card-shimmer-fast {
|
||
animation-duration: 3.5s;
|
||
background-size: 260% 260%;
|
||
}
|
||
|
||
@keyframes card-shimmer-sweep {
|
||
0% {
|
||
background-position: -120% -120%;
|
||
}
|
||
55% {
|
||
background-position: -120% -120%;
|
||
}
|
||
100% {
|
||
background-position: 220% 220%;
|
||
}
|
||
}
|
||
|
||
/* 边缘彩虹描边 — 营造光栅膜的"边光"质感(极轻)*/
|
||
.card-rainbow-edge {
|
||
position: absolute;
|
||
inset: 10rpx;
|
||
border-radius: 14rpx;
|
||
pointer-events: none;
|
||
box-shadow:
|
||
inset 0 0 6rpx rgba(200, 220, 240, 0.22),
|
||
inset 0 0 14rpx rgba(220, 230, 240, 0.12);
|
||
mix-blend-mode: screen;
|
||
}
|
||
|
||
.card-item.card-selected .card-rainbow-edge {
|
||
box-shadow:
|
||
inset 0 0 10rpx rgba(200, 230, 250, 0.35),
|
||
inset 0 0 22rpx rgba(230, 230, 240, 0.18);
|
||
animation: card-rainbow-pulse 4.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes card-rainbow-pulse {
|
||
0%, 100% {
|
||
filter: hue-rotate(0deg);
|
||
}
|
||
50% {
|
||
filter: hue-rotate(15deg);
|
||
}
|
||
}
|
||
|
||
@keyframes card-fade-in {
|
||
0% {
|
||
opacity: 0;
|
||
transform: scale(0.6);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: scale(var(--enter-scale, 1));
|
||
}
|
||
}
|
||
</style>
|
||
|