0712
This commit is contained in:
+299
-182
@@ -16,15 +16,15 @@ const sunEle = useTemplateRef('sun-ani');
|
||||
const sunEndEle = useTemplateRef('sun-ani-end');
|
||||
|
||||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const emit = defineEmits(['cloudUp', 'cloudDown', 'restart']);
|
||||
|
||||
async function shoot() {
|
||||
//sunEle.value?.jumpTo('天上飞')
|
||||
sunEle.value?.jumpTo('蓄力飞')
|
||||
|
||||
await document.querySelector('.sun-ani-wrapper')?.animate([
|
||||
{ transform: 'translateY(0)' },
|
||||
{ transform: 'translateY(8%)' },
|
||||
{ transform: 'translateY(0) translateX(-50%)' },
|
||||
{ transform: 'translateY(8%) translateX(-50%)' },
|
||||
], {
|
||||
duration: 900,
|
||||
easing: 'linear',
|
||||
@@ -41,15 +41,7 @@ async function shoot() {
|
||||
}).finished.then(() => {
|
||||
document.querySelector('.arrow')?.remove();
|
||||
});
|
||||
document.querySelectorAll('.cloud').forEach((ele) => {
|
||||
ele.animate([
|
||||
{ top: '300vw' },
|
||||
], {
|
||||
duration: 1000,
|
||||
easing: 'ease-in',
|
||||
fill: 'forwards',
|
||||
});
|
||||
});
|
||||
emit('cloudUp');
|
||||
document.querySelector('.bed')?.animate([
|
||||
{ transform: 'translateY(0)' },
|
||||
{ transform: 'translateY(150%)' },
|
||||
@@ -61,38 +53,30 @@ async function shoot() {
|
||||
});
|
||||
|
||||
await document.querySelector('.sun-ani-wrapper')?.animate([
|
||||
{ transform: 'translateY(8%)' },
|
||||
{ transform: 'translateY(-130%)' },
|
||||
{ transform: 'translateY(8%) translateX(-50%)', width: '60vw' },
|
||||
{ transform: 'translateY(-180%) translateX(-50%)', width: '50vw' },
|
||||
], {
|
||||
duration: 3000,
|
||||
easing: 'ease-in-out',
|
||||
duration: 3200,
|
||||
easing: 'cubic-bezier(0.99, 0.13, 0.35, 0.74)',
|
||||
fill: 'forwards',
|
||||
}).finished
|
||||
|
||||
document.querySelectorAll('.cloud.all').forEach((ele) => {
|
||||
ele.animate([
|
||||
{ top: '-50vw' },
|
||||
// @ts-ignore
|
||||
{ top: ele.style.top },
|
||||
], {
|
||||
duration: 1000,
|
||||
easing: 'ease-in',
|
||||
fill: 'forwards',
|
||||
});
|
||||
});
|
||||
canAction.value = true;
|
||||
gameLoop();
|
||||
|
||||
|
||||
emit('cloudDown');
|
||||
await document.querySelector('.sun-ani-wrapper')?.animate([
|
||||
{ transform: 'translateY(-130%)' },
|
||||
{ transform: 'translateY(-70%)' },
|
||||
{ transform: 'translateY(-180%) translateX(-50%)' },
|
||||
{ transform: 'translateY(-70%) translateX(-50%)' },
|
||||
], {
|
||||
duration: 2200,
|
||||
easing: 'ease',
|
||||
duration: 1200,
|
||||
easing: 'cubic-bezier(0.22, 0.61, 0.36, 1)',
|
||||
fill: 'forwards',
|
||||
}).finished
|
||||
|
||||
/* await wait(1000) */
|
||||
canAction.value = true;
|
||||
|
||||
|
||||
gameLoop();
|
||||
|
||||
}
|
||||
const gPos: Ref<'left' | 'center' | 'right'> = ref('center');
|
||||
const gDeg = computed(() => ({
|
||||
@@ -101,7 +85,7 @@ const gDeg = computed(() => ({
|
||||
right: 8
|
||||
})[gPos.value]);
|
||||
|
||||
const SPEED = 1.2;
|
||||
const SPEED = 1.8;
|
||||
const transitionOn = ref(true);
|
||||
let lastTime = 0;
|
||||
|
||||
@@ -118,7 +102,9 @@ const calcGiftDis = () => {
|
||||
} else return 0;
|
||||
}
|
||||
|
||||
const collisionThreshold = 20; // vw 单位
|
||||
const collisionThreshold = 10; // vw 单位
|
||||
const isPlayingDropAni = ref(false);
|
||||
|
||||
|
||||
function gameLoop(currentTime = 0) {
|
||||
let pauseFlag = false;
|
||||
@@ -127,7 +113,7 @@ function gameLoop(currentTime = 0) {
|
||||
giftPos.value = [null, null];
|
||||
giftShow.value = false;
|
||||
lastTime = currentTime;
|
||||
if (giftProgress.value >= giftList.length ) {
|
||||
if (giftProgress.value >= giftList.length) {
|
||||
// 游戏结束逻辑
|
||||
console.log('游戏结束!耗时:', timeSpent.value / 1000, '秒');
|
||||
gameEnd()
|
||||
@@ -141,55 +127,94 @@ function gameLoop(currentTime = 0) {
|
||||
timeSpent.value += deltaTime
|
||||
|
||||
// 移动距离 = 速度 * 时间间隔 / 16.67 (目标帧率约60fps)
|
||||
sunPos.value[1] -= SPEED * 0.3 * (deltaTime / 16.67);
|
||||
if (sunPos.value[1] < -100) {
|
||||
if (sunPos.value[1] < -50) {
|
||||
transitionOn.value = false;
|
||||
sunPos.value[1] = 154;
|
||||
} else {
|
||||
if (!isPlayingDropAni.value)
|
||||
(async () => {
|
||||
isPlayingDropAni.value = true;
|
||||
|
||||
// sunPos.y to -150, than y to 174 and drop to 124
|
||||
transitionOn.value = true;
|
||||
while (sunPos.value[1] > -150) {
|
||||
// 逐渐加速
|
||||
sunPos.value[1] -= SPEED * (deltaTime / 16.67) * 4;
|
||||
await wait(16.67 / 2);
|
||||
}
|
||||
sunPos.value[1] = -150;
|
||||
// 等待0.5秒
|
||||
|
||||
transitionOn.value = false;
|
||||
await wait(500);
|
||||
|
||||
sunPos.value[1] = 124;
|
||||
await wait(0);
|
||||
transitionOn.value = true;
|
||||
while (sunPos.value[1] > 66) {
|
||||
sunPos.value[1] -= SPEED * (deltaTime / 16.67) * 6;
|
||||
await wait(16.67 / 2);
|
||||
}
|
||||
// 最终位置 y = 66
|
||||
sunPos.value[1] = 66;
|
||||
isPlayingDropAni.value = false;
|
||||
|
||||
})();
|
||||
|
||||
} else if (!isPlayingDropAni.value) {
|
||||
transitionOn.value = true;
|
||||
sunPos.value[1] -= SPEED * 0.3 * (deltaTime / 16.67);
|
||||
}
|
||||
|
||||
if (giftPos.value[0] === null) {
|
||||
// 一直生成礼物,直到距离大于 collisionThreshold * 1.5
|
||||
while (true) {
|
||||
// randomly generate gift
|
||||
// x: -30 - 30
|
||||
// y: -20 - 60
|
||||
giftPos.value[0] = Math.random() * 60 - 30;
|
||||
giftPos.value[1] = Math.random() * 80 - 20;
|
||||
|
||||
if (calcGiftDis() > collisionThreshold * 1.5) {
|
||||
break; // 距离足够远,退出循环
|
||||
}
|
||||
}
|
||||
}
|
||||
const moveGift = () => {
|
||||
if (giftPos.value[0] === null) {
|
||||
// 一直生成礼物,直到距离大于 collisionThreshold * 1.5
|
||||
while (true) {
|
||||
// 生成起始点
|
||||
giftStartPos.value[0] = Math.random() * 60 - 30;
|
||||
giftStartPos.value[1] = Math.random() * 80 - 20;
|
||||
|
||||
// gift slowly fly to the edge
|
||||
if (giftPos.value[0] !== null && giftPos.value[1] !== null) {
|
||||
const giftSpeed = 0.3 * (deltaTime / 16.67);
|
||||
// 生成终点
|
||||
giftEndPos.value[0] = Math.random() * 60 - 30;
|
||||
giftEndPos.value[1] = Math.random() * 80 - 20;
|
||||
|
||||
// 根据礼物的位置决定移动方向
|
||||
if (giftPos.value[0] < 0) {
|
||||
// 向左边缘移动
|
||||
giftPos.value[0] -= giftSpeed * 0.2;
|
||||
if (giftPos.value[0] < -45) {
|
||||
giftPos.value[0] = -45; // 到达左边缘后停止
|
||||
}
|
||||
} else {
|
||||
// 向右边缘移动
|
||||
giftPos.value[0] += giftSpeed * 0.2;
|
||||
if (giftPos.value[0] > 45) {
|
||||
giftPos.value[0] = 45; // 到达右边缘后停止
|
||||
// 初始位置为起始点
|
||||
giftPos.value[0] = giftStartPos.value[0];
|
||||
giftPos.value[1] = giftStartPos.value[1];
|
||||
|
||||
// 重置移动进度和方向
|
||||
giftMoveProgress.value = 0;
|
||||
giftMoveDirection.value = 1;
|
||||
|
||||
if (calcGiftDis() > collisionThreshold * 1.5) {
|
||||
break; // 距离足够远,退出循环
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// y 方向始终向上移动
|
||||
giftPos.value[1] += giftSpeed * 0.3;
|
||||
if (giftPos.value[1] > 60) {
|
||||
giftPos.value[1] = 60; // 到达上边缘后停止
|
||||
// gift move between two points
|
||||
if (giftPos.value[0] !== null && giftPos.value[1] !== null) {
|
||||
const giftSpeed = 0.8 * (deltaTime / 16.67); // 速度加快 (原来是0.3)
|
||||
|
||||
// 更新移动进度
|
||||
giftMoveProgress.value += giftSpeed * giftMoveDirection.value * 0.01;
|
||||
|
||||
// 检查是否到达端点,如果是则反转方向
|
||||
if (giftMoveProgress.value >= 1) {
|
||||
giftMoveProgress.value = 1;
|
||||
giftMoveDirection.value = -1;
|
||||
} else if (giftMoveProgress.value <= 0) {
|
||||
giftMoveProgress.value = 0;
|
||||
giftMoveDirection.value = 1;
|
||||
}
|
||||
|
||||
// 使用线性插值计算当前位置
|
||||
giftPos.value[0] = giftStartPos.value[0] + (giftEndPos.value[0] - giftStartPos.value[0]) * giftMoveProgress.value;
|
||||
giftPos.value[1] = giftStartPos.value[1] + (giftEndPos.value[1] - giftStartPos.value[1]) * giftMoveProgress.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
moveGift();
|
||||
|
||||
// 计算太阳和礼物之间的距离
|
||||
const distance = calcGiftDis()
|
||||
@@ -199,9 +224,7 @@ function gameLoop(currentTime = 0) {
|
||||
console.log('太阳收集到礼物!', { sunPos: sunPos.value, giftPos: giftPos.value, distance });
|
||||
|
||||
pauseFlag = true; // 暂停游戏循环
|
||||
// TODO: 在这里添加碰撞后的逻辑
|
||||
// 例如:播放音效、更新分数、显示特效、切换到下一个礼物等
|
||||
giftShow.value = true; // 隐藏礼物
|
||||
giftShow.value = true; // 显示展示礼物界面
|
||||
document.querySelector('.gift-item.show')?.animate([
|
||||
{ transform: 'scale(1)' },
|
||||
{ transform: 'scale(0)' },
|
||||
@@ -210,7 +233,7 @@ function gameLoop(currentTime = 0) {
|
||||
easing: 'cubic-bezier(0.42,-0.98, 0.4, 1)',
|
||||
fill: 'forwards',
|
||||
}).finished.then(() => {
|
||||
giftPos.value = [null, null]; // 隐藏礼物
|
||||
giftPos.value = [null, null]; // 重置游戏中礼物
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,6 +253,18 @@ function gToggle(pos: 'left' | 'center' | 'right') {
|
||||
fill: 'forwards',
|
||||
});
|
||||
|
||||
if (isPlayingDropAni.value) {
|
||||
// 如果正在下落动画中,直接返回
|
||||
return;
|
||||
}
|
||||
|
||||
if (sunPos.value[0] < -36
|
||||
|| sunPos.value[0] > 36
|
||||
|| sunPos.value[1] > 70) {
|
||||
// 太阳位置不在范围内,直接返回
|
||||
return;
|
||||
}
|
||||
|
||||
const windAniMap: Record<'left' | 'center' | 'right', [number, [number, number], [number, number]]> = {
|
||||
left: [
|
||||
-60,
|
||||
@@ -270,6 +305,12 @@ function gToggle(pos: 'left' | 'center' | 'right') {
|
||||
})[pos].forEach((v, i) => {
|
||||
sunPos.value[i] += v * SPEED * 2;
|
||||
});
|
||||
|
||||
if (sunPos.value[0] < -36) {
|
||||
sunPos.value[0] = -36;
|
||||
} else if (sunPos.value[0] > 36) {
|
||||
sunPos.value[0] = 36;
|
||||
}
|
||||
}
|
||||
|
||||
const giftShow = ref(false);
|
||||
@@ -280,6 +321,10 @@ const canAction = ref(false);
|
||||
const sunPos = ref([0, 0]) // x, y
|
||||
|
||||
const giftPos: Ref<[number | null, number | null]> = ref([null, null]); // x, y
|
||||
const giftStartPos: Ref<[number, number]> = ref([0, 0]); // 起始点
|
||||
const giftEndPos: Ref<[number, number]> = ref([0, 0]); // 终点
|
||||
const giftMoveProgress = ref(0); // 移动进度 0-1
|
||||
const giftMoveDirection = ref(1); // 移动方向 1正向 -1反向
|
||||
|
||||
const giftList = assets.icons;
|
||||
|
||||
@@ -469,6 +514,30 @@ const showEndAni = ref(false);
|
||||
|
||||
async function gameEnd() {
|
||||
|
||||
fetch('/api/score', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
region: props.userdata.region,
|
||||
store: props.userdata.store,
|
||||
name: props.userdata.username,
|
||||
time: timeSpent.value / 1000,
|
||||
})
|
||||
}).then(_ => {
|
||||
fetch('/api/score', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
leaderBoard.value = data;
|
||||
});
|
||||
})
|
||||
|
||||
document.querySelector('.bed')?.animate([
|
||||
{ transform: 'translateY(150%)' },
|
||||
{ transform: 'translateY(0)' },
|
||||
@@ -485,14 +554,15 @@ async function gameEnd() {
|
||||
img.style.animationDirection = 'reverse';
|
||||
});
|
||||
|
||||
await document.querySelector('.sun-ani-wrapper')?.animate([
|
||||
{ transform: 'translateY(8%)', left: '19%', bottom: '23%' },
|
||||
document.querySelector('.sun-ani-wrapper')?.animate([
|
||||
{ transform: 'translateY(8%) translateX(-50%)', left: '50%', bottom: '23%', width: '60vw' },
|
||||
], {
|
||||
duration: 700,
|
||||
duration: 1800,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards',
|
||||
}).finished
|
||||
|
||||
|
||||
isGameEnd.value = true;
|
||||
|
||||
// 模拟图标聚集并获取最终位置
|
||||
@@ -501,13 +571,7 @@ async function gameEnd() {
|
||||
await wait(0)
|
||||
floatIcons.value = finalPositions;
|
||||
|
||||
const element = document.querySelector('.sun-ani-wrapper');
|
||||
if (!element) return;
|
||||
|
||||
await element.animate(
|
||||
{ transform: 'translateY(8%)' },
|
||||
{ duration: 2500, easing: 'ease-in-out', fill: 'forwards' }
|
||||
).finished;
|
||||
await wait(1800)
|
||||
|
||||
showEndAni.value = true;
|
||||
sunEndEle.value?.jumpTo('all');
|
||||
@@ -530,12 +594,6 @@ async function gameEnd() {
|
||||
|
||||
|
||||
}
|
||||
/* setTimeout(() => {
|
||||
canAction.value = true
|
||||
gameEnd()
|
||||
|
||||
}, 100); */
|
||||
|
||||
|
||||
const leaderBoard = ref([
|
||||
{ name: '始祖鸟', time: 50, region: '奥莱北区', store: "上海始祖鸟阿尔法中心" },
|
||||
@@ -559,15 +617,13 @@ const showScore = ref(false)
|
||||
|
||||
const showLastPage = ref(false);
|
||||
|
||||
|
||||
const sunAniEndRules = ref([
|
||||
{ name: 'all', frame: 165, loop: 1, pauseAfter: false, duration: 33 },
|
||||
{ name: '左右跳', frame: 66, loop: 0, pauseAfter: true, duration: 33 },
|
||||
{ name: '落下', frame: 32, loop: 1, pauseAfter: false, duration: 33 },
|
||||
{ name: 'loop', frame: 33, loop: 0, pauseAfter: true, duration: 33 },
|
||||
])
|
||||
async function finishCollect() {
|
||||
document.querySelector('.bar-container')?.animate([
|
||||
{ transform: 'translateY(300%)' }], {
|
||||
duration: 500,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards',
|
||||
})
|
||||
|
||||
document.querySelector('.finish-collect')?.animate([
|
||||
{ transform: 'scale(1)' },
|
||||
{ transform: 'scale(0)' },
|
||||
@@ -591,48 +647,98 @@ async function finishCollect() {
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards',
|
||||
}).finished
|
||||
await sunEndEle.value?.jumpToSoftly('落下');
|
||||
document.querySelector('.bar-container')?.animate([
|
||||
{ transform: 'translateY(300%)' }], {
|
||||
duration: 500,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards',
|
||||
})
|
||||
|
||||
|
||||
showLastPage.value = true;
|
||||
}
|
||||
|
||||
// 重置游戏状态的函数
|
||||
function resetGame() {
|
||||
return
|
||||
// 重置所有响应式状态到初始值
|
||||
gPos.value = 'center';
|
||||
transitionOn.value = true;
|
||||
lastTime = 0;
|
||||
giftShow.value = false;
|
||||
canAction.value = false;
|
||||
sunPos.value = [0, 0];
|
||||
giftPos.value = [null, null];
|
||||
giftStartPos.value = [0, 0];
|
||||
giftEndPos.value = [0, 0];
|
||||
giftMoveProgress.value = 0;
|
||||
giftMoveDirection.value = 1;
|
||||
giftProgress.value = 0;
|
||||
isGameEnd.value = false;
|
||||
timeSpent.value = 0;
|
||||
showEndAni.value = false;
|
||||
showScore.value = false;
|
||||
showLastPage.value = false;
|
||||
|
||||
// 重新生成图标位置
|
||||
floatIcons.value = generateIconPositions();
|
||||
|
||||
// 重置动画元素到初始状态
|
||||
sunEle.value?.jumpTo('起飞前');
|
||||
sunEndEle.value?.jumpTo('loop');
|
||||
|
||||
// 清除所有动画和样式
|
||||
const elementsToReset = [
|
||||
'.bed', '.sun-ani-wrapper', '.p-machine', '.action img',
|
||||
'.gift-item', '.wind', '.bar-container', '.finish-collect',
|
||||
'.wecare-title', '.lines'
|
||||
];
|
||||
|
||||
elementsToReset.forEach(selector => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element instanceof HTMLElement) {
|
||||
element.getAnimations().forEach(animation => animation.cancel());
|
||||
element.style.animation = '';
|
||||
element.style.transform = '';
|
||||
element.style.opacity = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page game">
|
||||
|
||||
<div class="public">
|
||||
<div class="bg"> </div>
|
||||
<img class="cloud" src="../assets/game/云.png" v-for="c in [
|
||||
{ w: 60, t: 10, l: 75, all: true },
|
||||
{ w: 75, t: 55, l: -45 },
|
||||
{ w: 40, t: 105, l: 35 },
|
||||
]" :style="{ width: `${c.w}vw`, top: `${c.t}vw`, left: `${c.l}vw` }" :class="{ all: c.all }"></img>
|
||||
|
||||
<img src="../assets/game/床.png" alt="" class="abs bed" style="width: 66vw;bottom: 12%;left: 17%;">
|
||||
<img src="../assets/game/床.png" alt="" class="abs bed"
|
||||
style="width: 66vw;bottom: 12%;left: 17%;">
|
||||
|
||||
<AniEle :url="assets.ani.下拉蓄力提示" ref="aniEle" :height="334" :width="337" :rules="[
|
||||
{ name: '蓄力', frame: 241, loop: 2 },
|
||||
]" class="abs arrow" style="bottom: 7%;width: 30vw;left: 35%;" @pointerdown.once="shoot" />
|
||||
|
||||
|
||||
<img class="cloud" src="../assets/game/云.png" v-for="c in [
|
||||
{ w: 75, t: 165, l: -35, all: true },
|
||||
{ w: 100, t: 144, l: 60, all: true },
|
||||
]" :style="{ width: `${c.w}vw`, top: `${c.t}vw`, left: `${c.l}vw` }" :class="{ all: c.all }"></img>
|
||||
<AniEle :url="assets.ani.下拉蓄力提示" ref="aniEle" :height="334"
|
||||
:width="337" :rules="[
|
||||
{ name: '蓄力', frame: 241, loop: 2 },
|
||||
]" class="abs arrow" style="bottom: 7%;width: 30vw;left: 35%;"
|
||||
@pointerdown.once="shoot" />
|
||||
|
||||
<div class="sun-ani-wrapper abs" v-if="!showEndAni" :style="{
|
||||
width: '60vw',
|
||||
zIndex: 99,
|
||||
pointerEvents: 'none',
|
||||
bottom: `calc(21% + ${sunPos[1]}vw)`,
|
||||
left: `calc(19% + ${sunPos[0]}vw)`,
|
||||
left: `calc(49% + ${sunPos[0]}vw)`,
|
||||
transform: `translateX(-50%)`,
|
||||
transition: transitionOn ? 'all 0.1s ease' : undefined
|
||||
}">
|
||||
|
||||
<!-- AniEle 组件现在只关心自己的内容,不再有复杂的外部样式 -->
|
||||
<AniEle :url="assets.ani.小太阳总" ref="sun-ani" class="sun-ani" :height="1800" :width="1600" :rules="[
|
||||
{ name: '起飞前', frame: 90, loop: 0, pauseAfter: true, duration: 33 },
|
||||
{ name: '蓄力飞', frame: 181, loop: 1, pauseAfter: false, duration: 33 },
|
||||
{ name: '天上飞', frame: 90, loop: 0, pauseAfter: true, duration: 33 },
|
||||
{ name: '落下', frame: 393, loop: 0, pauseAfter: false, duration: 33 },
|
||||
]" style="width: 100%; height: auto;" />
|
||||
<AniEle :url="assets.ani.小太阳总" ref="sun-ani" class="sun-ani"
|
||||
:height="1800" :width="1600" :rules="[
|
||||
{ name: '起飞前', frame: 90, loop: 0, pauseAfter: true, duration: 33 },
|
||||
{ name: '蓄力飞', frame: 181, loop: 1, pauseAfter: false, duration: 33 },
|
||||
{ name: '天上飞', frame: 90, loop: 0, pauseAfter: true, duration: 33 },
|
||||
{ name: '落下', frame: 393, loop: 0, pauseAfter: false, duration: 33 },
|
||||
]" style="width: 100%; height: auto;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -641,48 +747,58 @@ async function finishCollect() {
|
||||
style="bottom: -40%; width: 66vw; left: 17%; transform-origin: 50% 63%; transition: all 0.2s;"
|
||||
:style="{ transform: `rotate(${gDeg}deg)` }" />
|
||||
<div class="action">
|
||||
<img draggable="false" :style="{ animationDelay: '0.1s' }" src="../assets/game/左.png" alt=""
|
||||
@click="gToggle('left')">
|
||||
<img draggable="false" :style="{ animationDelay: '0.2s' }" src="../assets/game/中.png" alt=""
|
||||
<img draggable="false" :style="{ animationDelay: '0.1s' }"
|
||||
src="../assets/game/左.png" alt="" @click="gToggle('left')">
|
||||
<img draggable="false" :style="{ animationDelay: '0.2s' }"
|
||||
src="../assets/game/中.png" alt=""
|
||||
@click="gToggle('center')">
|
||||
<img draggable="false" :style="{ animationDelay: '0.0s' }" src="../assets/game/右.png" alt=""
|
||||
@click="gToggle('right')">
|
||||
<img draggable="false" :style="{ animationDelay: '0.0s' }"
|
||||
src="../assets/game/右.png" alt="" @click="gToggle('right')">
|
||||
</div>
|
||||
|
||||
<img class="abs gift-item" v-for="(gift, index) in giftList" :src="gift[0]" style="width: 30vw;"
|
||||
v-if="!isGameEnd" :style="{
|
||||
<img class="abs gift-item" v-for="(gift, index) in giftList"
|
||||
:src="gift[0]" style="width: 20vw;" v-if="!isGameEnd" :style="{
|
||||
display: giftProgress == index ? 'block' : 'none',
|
||||
bottom: `calc(48% + ${giftPos[1] ?? 0}vw)`, left: `calc(34.5% + ${giftPos[0] ?? 0}vw)`, transition: transitionOn ? `all 0.1s ease` : undefined
|
||||
}" alt="" :class="{ show: giftProgress == index, hide: giftProgress != index }">
|
||||
bottom: `calc(45% + ${giftPos[1] ?? 0}vw)`, left: `calc(39.5% + ${giftPos[0] ?? 0}vw)`, transition: transitionOn ? `all 0.1s ease` : undefined
|
||||
}" alt=""
|
||||
:class="{ show: giftProgress == index, hide: giftProgress != index }">
|
||||
|
||||
<img src="../assets//game/操作提示.png" class="abs prompt" alt="" style="width: 31%; bottom: 18%; left: 34%; ">
|
||||
<img src="../assets//game/操作提示.png" class="abs prompt" alt=""
|
||||
style="width: 31%; bottom: 18%; left: 34%; ">
|
||||
<img src="../assets/game/风.png" alt="" class="abs wind">
|
||||
<img src="../assets/game/wecare.png" alt="" class="abs wecare" style="width: 24vw;
|
||||
<img src="../assets/game/wecare.png" alt="" class="abs wecare"
|
||||
style="width: 24vw;
|
||||
left: 50%; top: 6%; transform: translateX(-50%);">
|
||||
<div class="dot" v-if="!isGameEnd">
|
||||
<div v-for="_ in Array(giftProgress)" class="dot-item finished"></div>
|
||||
<div v-for="_ in Array(giftList.length - giftProgress)" class="dot-item"></div>
|
||||
<div v-for="(_, index) in giftList" :key="index"
|
||||
class="dot-item" :class="{ finished: index < giftProgress }"
|
||||
@click="giftProgress = index">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="game-end abs" v-if="isGameEnd" style="z-index: 2; inset: 0; height: 100%; width: 100%;">
|
||||
<div class="icon-cloud" style="position: absolute; left: 50%; top: 20%;">
|
||||
<div class="game-end abs" v-if="isGameEnd"
|
||||
style="z-index: 2; inset: 0; height: 100%; width: 100%;">
|
||||
<div class="icon-cloud"
|
||||
style="position: absolute; left: 50%; top: 20%;">
|
||||
<img v-for="icon in floatIcons" :src="icon.src" alt="" :style="{
|
||||
top: `${icon.top}vw`,
|
||||
left: `${icon.left}vw`,
|
||||
animationDelay: `${icon.delay}ms`,
|
||||
}">
|
||||
</div>
|
||||
<img src="../assets/game/wecare.png" alt="" class="abs wecare" style="width: 24vw;
|
||||
<img src="../assets/game/wecare.png" alt="" class="abs wecare"
|
||||
style="width: 24vw;
|
||||
left: 50%; top: 6%; transform: translateX(-50%);">
|
||||
|
||||
<img src="../assets/game/wecare标题.png" alt="" class="abs wecare-title"
|
||||
style="width: 72%;left: 14%;top: 12%;">
|
||||
<img src="../assets/game/wecare标题.png" alt=""
|
||||
class="abs wecare-title" style="width: 72%;left: 14%;top: 12%;">
|
||||
|
||||
|
||||
<div class="scoreboard" v-if="showScore">
|
||||
<div class="bar-container">
|
||||
<div class="bar" v-for="(item, index) in [leaderBoard[1], leaderBoard[0], leaderBoard[2]]"
|
||||
<div class="bar"
|
||||
v-for="(item, index) in [leaderBoard[1], leaderBoard[0], leaderBoard[2]]"
|
||||
:key="index" :class="{
|
||||
'first': index === 1, 'second': index === 0, 'third': index === 2
|
||||
}">
|
||||
@@ -693,7 +809,9 @@ async function finishCollect() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="lines">
|
||||
<div class="line" v-for="(item, index) in leaderBoard.slice(3, 7)" :key="index"
|
||||
<div class="line"
|
||||
v-for="(item, index) in leaderBoard.slice(3, 7)"
|
||||
:key="index"
|
||||
:style="{ animationDelay: index * 100 + 400 + 'ms' }">
|
||||
<div class="rank">{{ index + 4 }}</div>
|
||||
<div class="name">{{ item.name }}</div>
|
||||
@@ -703,46 +821,53 @@ async function finishCollect() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sun-ani-end-wrapper abs" style="pointer-events: none; inset: 0; width: 100%;z-index: 2;"
|
||||
<div class="sun-ani-end-wrapper abs"
|
||||
style="pointer-events: none; inset: 0; width: 100%;z-index: 2;"
|
||||
:style="{ display: showEndAni ? 'block' : 'none' }">
|
||||
|
||||
<!-- AniEle 组件现在只关心自己的内容,不再有复杂的外部样式 -->
|
||||
<AniEle :url="assets.ani.后端效果" ref="sun-ani-end" class="sun-ani-end" :height="2462" :width="1179"
|
||||
:rules="[
|
||||
{ name: 'all', frame: 263, loop: 1, pauseAfter: false, duration: 33 },
|
||||
{ name: 'loop', frame: 33, loop: 0, pauseAfter: true, duration: 33 },
|
||||
]" style="width: 100%; height: auto;" />
|
||||
<AniEle :url="assets.ani.后端效果" ref="sun-ani-end"
|
||||
class="sun-ani-end" :height="2462" :width="1179"
|
||||
:rules="sunAniEndRules"
|
||||
style="width: 100%; height: auto;" />
|
||||
</div>
|
||||
|
||||
<img src="../assets/game/完成收集.png" alt="" v-if="showScore" class="finish-collect abs" @click.once="finishCollect"
|
||||
<img src="../assets/game/完成收集.png" alt="" v-if="showScore"
|
||||
class="finish-collect abs" @click.once="finishCollect"
|
||||
style="bottom: 8%;left: 33%;width: 34vw;animation: scale-in 0.3s ease-out;">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="last-page" v-if="showLastPage"
|
||||
style="z-index: 0;position: absolute; inset: 0;height: 100%; width: 100%;">
|
||||
style="z-index: 0;position: absolute; inset: 0;height: 100%; width: 100%; pointer-events: none;">
|
||||
<img src="../assets/game/end_right_bg.png" alt="" style="position: absolute;top: 29.3%;left: 37.2%;width: 57.4%; height: 38.1%;
|
||||
animation: fade-in 3s ease-in-out forwards; opacity: 0;">
|
||||
">
|
||||
<AniEle :url="assets.ani.结尾" ref="gui-ani-end" class="gui-ani-end" :height="2462" :width="1179" :rules="[
|
||||
{ name: 'all', frame: 105, loop: 1, pauseAfter: false, duration: 16 },
|
||||
]" style="width: 100%; height: auto;position: absolute;inset: 0;pointer-events: none;" />
|
||||
|
||||
<AniEle :url="assets.ani.结尾" ref="gui-ani-end" class="gui-ani-end"
|
||||
:height="2462" :width="1179" :rules="[
|
||||
{ name: 'all', frame: 105, loop: 1, pauseAfter: false, duration: 16 },
|
||||
]"
|
||||
style="width: 100%; height: auto;position: absolute;inset: 0;pointer-events: none;" />
|
||||
<AniEle :url="assets.ani.主标出现" :width="1015" :height="336"
|
||||
:rules="[{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }]"
|
||||
style="position: absolute;top: 13%;width: 84%;left: 8%; pointer-events:none;" ref="main-logo" />
|
||||
<img src="../assets/game/床.png" alt="" class="abs last-bed" style="width: 66vw;bottom: -19%;left: 17%;">
|
||||
style="position: absolute;top: 13%;width: 84%;left: 8%; pointer-events:none;"
|
||||
ref="main-logo" />
|
||||
<img src="../assets/game/床.png" alt="" class="abs last-bed"
|
||||
style="width: 66vw;bottom: -19%;left: 17%;">
|
||||
|
||||
<img src="../assets/game/分享海报.png" alt="" class="abs"
|
||||
style="width: 34vw;bottom: -19%;left: 15%;animation: last-btn-in 0.3s ease-out forwards;">
|
||||
<img src="../assets/game/再玩一次.png" alt="" class="abs"
|
||||
style="width: 34vw;bottom: -19%;left: 51%;animation: last-btn-in 0.3s ease-out forwards; animation-delay: 200ms;">
|
||||
@click="resetGame"
|
||||
style="width: 34vw;bottom: -19%;left: 51%;animation: last-btn-in 0.3s ease-out forwards; animation-delay: 200ms; cursor: pointer;">
|
||||
|
||||
<div class="time-spent abs" style="left: 10%; top:44%;width: 24%; display: flex;
|
||||
align-items: center; flex-direction: column;">
|
||||
<div class="line-1"
|
||||
style="display: flex; align-items: center; gap: 1vw; animation: line-in 0.5s ease-out 0.6s forwards;transform: translateX(-210%);;">
|
||||
<div class="username" style="font-size: 2.9vw;">{{ userdata.username }}</div>
|
||||
style="display: flex; align-items: center; gap: 1vw; animation: line-in 0.5s ease-out 0.6s forwards;transform: translateX(-210%);">
|
||||
<div class="username" style="font-size: 2.9vw;">{{
|
||||
userdata.username }}</div>
|
||||
<div class="cost-info"
|
||||
style="font-size: 2.9vw; color: white; display: flex;align-items: center;justify-content: center;background-color: black;padding: 0 1.2vw; height: 4.5vw; border-radius: 4vw;">
|
||||
用时</div>
|
||||
@@ -750,13 +875,17 @@ async function finishCollect() {
|
||||
|
||||
<div class="time"
|
||||
style="font-size: 9vw;animation: line-in 0.5s ease-out 0.8s forwards;transform: translateX(-210%);">
|
||||
{{ fmtTime(timeSpent/1000) }}</div>
|
||||
{{ fmtTime(timeSpent / 1000) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="region-area abs" style="left: 10%; top:30%;width: 24%;height: 10%;gap:2vw; display: flex;animation: line-in 0.5s ease-out 0.4s forwards;transform: translateX(-210%);
|
||||
<div class="region-area abs"
|
||||
style="left: 10%; top:30%;width: 24%;height: 10%;gap:2vw; display: flex;animation: line-in 0.5s ease-out 0.4s forwards;transform: translateX(-210%);
|
||||
align-items: center; flex-direction: column;justify-content: center;">
|
||||
<div class="region" style="width: 20vw; background-color: white;font-size: 3vw;text-align: center;height: 4.5vw;line-height: 4.5vw; border-radius: 4.5vw;">{{ userdata.region }}</div>
|
||||
<div class="store" style="font-size: 2.8vw; color: gray;">{{ userdata.store }}</div>
|
||||
<div class="region"
|
||||
style="width: 20vw; background-color: white;font-size: 3vw;text-align: center;height: 4.5vw;line-height: 4.5vw; border-radius: 4.5vw;">
|
||||
{{ userdata.region }}</div>
|
||||
<div class="store" style="font-size: 2.8vw; color: gray;">{{
|
||||
userdata.store }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -764,9 +893,10 @@ async function finishCollect() {
|
||||
|
||||
|
||||
<div v-if="giftShow" class="gift-popup">
|
||||
<img :src="giftList[giftProgress][1]" alt="" class="abs" style="width: 82%; left: 9%; top: 11%;">
|
||||
<img src="../assets/game/收下福利.png" alt="" class="abs" style="width: 14%; left: 43%; top: 54%;"
|
||||
@click="gameLoop()">
|
||||
<img :src="giftList[giftProgress][1]" alt="" class="abs"
|
||||
style="width: 82%; left: 9%; top: 11%;">
|
||||
<img src="../assets/game/收下福利.png" alt="" class="abs"
|
||||
style="width: 14%; left: 43%; top: 54%;" @click="gameLoop()">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1111,17 +1241,4 @@ async function finishCollect() {
|
||||
animation: scale-in 0.4s ease-in-out forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
background-image: url('../assets/game/蓝天.png');
|
||||
}
|
||||
|
||||
.cloud {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import assets from '../assets';
|
||||
|
||||
const progress = ref(0);
|
||||
const total = Object.keys(assets.ani).length;
|
||||
let completedFiles = 0;
|
||||
|
||||
// 使用 XMLHttpRequest 下载单个文件并跟踪进度
|
||||
const downloadFile = (url: string, key: string): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'blob';
|
||||
|
||||
// 跟踪下载进度
|
||||
xhr.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
const fileProgress = (event.loaded / event.total) * 100;
|
||||
// 计算总体进度:已完成文件的进度 + 当前文件的进度
|
||||
const totalProgress = (completedFiles + fileProgress / 100) / total * 100;
|
||||
progress.value = Math.min(100, totalProgress);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
const blob = xhr.response;
|
||||
const objectURL = URL.createObjectURL(blob);
|
||||
assets.ani[key as keyof typeof assets.ani] = objectURL;
|
||||
completedFiles++;
|
||||
progress.value = Math.min(100, completedFiles / total * 100);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`资源加载失败: ${url}, 状态码: ${xhr.status}`));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
reject(new Error(`网络错误,无法加载: ${url}`));
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
};
|
||||
|
||||
const emit = defineEmits(['loaded']);
|
||||
// 开始加载所有资源
|
||||
(async () => {
|
||||
try {
|
||||
for (const key in assets.ani) {
|
||||
if (Object.prototype.hasOwnProperty.call(assets.ani, key)) {
|
||||
const url = assets.ani[key as keyof typeof assets.ani];
|
||||
await downloadFile(url, key);
|
||||
}
|
||||
}
|
||||
console.log('所有资源加载完成:', assets);
|
||||
// 触发加载完成事件
|
||||
await document.querySelector('.loader-container')?.animate([
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 }
|
||||
], {
|
||||
duration: 200,
|
||||
easing: 'ease-in',
|
||||
fill: 'forwards',
|
||||
}).finished;
|
||||
|
||||
emit('loaded');
|
||||
} catch (error) {
|
||||
console.error('资源加载失败:', error);
|
||||
if (confirm('资源加载失败,是否重试?' + error)) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="loader-container">
|
||||
<img src="../assets/loader/wecare.png" alt=""
|
||||
style="position: absolute; left: 50%; top:40%; transform: translateX(-50%); width: 40vw; ">
|
||||
|
||||
|
||||
<div class="progress-container"
|
||||
style="position: absolute; left: 50%; top: 48%; transform: translateX(-50%); width: 40vw; background-color: white; height: 2vw; border-radius: 2vw; overflow: hidden;">
|
||||
<div class="progress"
|
||||
style="height: 100%; background-color: #FF7B1D;transition: all 0.1s;"
|
||||
:style="{ width: progress + '%' }"></div>
|
||||
</div>
|
||||
|
||||
<div class="info"
|
||||
style="position: absolute; left: 50%; top: 52%; transform: translateX(-50%); font-size: 3vw;">
|
||||
加载中...</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
+288
-334
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import assets from '../assets';
|
||||
|
||||
import { regions, type RegionData, type Store } from '../data';
|
||||
@@ -19,14 +19,16 @@ const showStoreSelector = ref(false);
|
||||
const selectedRegion = ref<string>('');
|
||||
const selectedStores = ref<Store[]>([]);
|
||||
const selectedStoreIndex = ref(0);
|
||||
const selectedBubbleEl = ref<HTMLElement | null>(null);
|
||||
const selectionInProgress = ref(false);
|
||||
|
||||
// 区域映射
|
||||
const regionKeyMap: { [key: string]: keyof RegionData | 'combined' } = {
|
||||
'大北区': '大北区',
|
||||
'大东区': '大东区',
|
||||
'大南区&免税': 'combined', // 需要合并大南区和免税
|
||||
'大南区&免税': 'combined',
|
||||
'大西区': '大西区',
|
||||
'奥莱': 'combined', // 需要合并奥莱北区和奥莱东区
|
||||
'奥莱': 'combined',
|
||||
'博物馆': '博物馆',
|
||||
};
|
||||
|
||||
@@ -49,17 +51,13 @@ function getRegionStores(regionKey: string): Store[] {
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
||||
function initializeStoreSelector() {
|
||||
// 等待DOM渲染完成
|
||||
setTimeout(() => {
|
||||
const storeItems = document.querySelectorAll('.store-item');
|
||||
|
||||
if (storeItems.length === 0) return;
|
||||
|
||||
// 创建 Intersection Observer
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
// 找到正在相交的元素的索引
|
||||
const index = Array.from(storeItems).indexOf(entry.target);
|
||||
if (index !== -1 && index !== selectedStoreIndex.value) {
|
||||
selectedStoreIndex.value = index;
|
||||
@@ -68,11 +66,10 @@ function initializeStoreSelector() {
|
||||
});
|
||||
}, {
|
||||
root: document.querySelector('.store-list'),
|
||||
rootMargin: '-45% 0px -45% 0px', // 只有在中间10%区域的元素会被检测到
|
||||
rootMargin: '-45% 0px -45% 0px',
|
||||
threshold: 0.5
|
||||
});
|
||||
|
||||
// 观察所有店铺项目
|
||||
storeItems.forEach(item => {
|
||||
observer?.observe(item);
|
||||
});
|
||||
@@ -81,7 +78,6 @@ function initializeStoreSelector() {
|
||||
|
||||
function selectStore(index: number) {
|
||||
selectedStoreIndex.value = index;
|
||||
// 滚动到选中的项目,让 scroll-snap 自动处理定位
|
||||
const storeItems = document.querySelectorAll('.store-item');
|
||||
if (storeItems[index]) {
|
||||
storeItems[index].scrollIntoView({
|
||||
@@ -91,161 +87,79 @@ function selectStore(index: number) {
|
||||
}
|
||||
}
|
||||
|
||||
// 清理 observer
|
||||
function cleanupObserver() {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'startExploration', payload: { region: string; store: string, username: string }): void;
|
||||
}>();
|
||||
function startExploration() {
|
||||
// 这里可以添加开始探索的逻辑
|
||||
|
||||
const noticeToInputName = ref(false);
|
||||
async function startExploration() {
|
||||
console.log('开始探索:', selectedStores.value[selectedStoreIndex.value]);
|
||||
if (!selectedRegion.value || selectedStores.value.length === 0) {
|
||||
alert('请选择一个区域和店铺!');
|
||||
return;
|
||||
}
|
||||
if ((document.querySelector('.name-input') as HTMLInputElement).value.trim() === '') {
|
||||
alert('请输入你的名字!');
|
||||
const nameInput = document.querySelector('.name-input') as HTMLInputElement;
|
||||
if (nameInput.value.trim() === '') {
|
||||
noticeToInputName.value = true;
|
||||
return;
|
||||
}
|
||||
startExplorationRules.value[0].reverse = true;
|
||||
startExplorationRules.value[0].reverse = true;
|
||||
// 2. 将放大的气泡恢复原状
|
||||
|
||||
let bubble = selectedBubbleEl.value;
|
||||
if (!bubble) return;
|
||||
|
||||
bubble.classList.remove('zoomed');
|
||||
bubble.style.transform = 'translate3d(-50%, -50%, 0) scale(0)';
|
||||
|
||||
document.querySelector('.sun-wrap')?.classList.add('fade-out');
|
||||
|
||||
emit('startExploration', {
|
||||
region: selectedRegion.value,
|
||||
store: selectedStores.value[selectedStoreIndex.value].店铺,
|
||||
username: (document.querySelector('.name-input') as HTMLInputElement).value
|
||||
username: nameInput.value
|
||||
});
|
||||
}
|
||||
|
||||
// 动画管理
|
||||
let fadeInAnimations: Animation[] = [];
|
||||
let swapInAnimations: Animation[] = [];
|
||||
let bubbleDropAnimations: Animation[] = [];
|
||||
|
||||
// 保存所有动画的数组
|
||||
let allAnimations: Animation[] = [];
|
||||
|
||||
// 淡入动画
|
||||
function createFadeInAnimation(element: Element, delay: number = 0) {
|
||||
const animation = element.animate([
|
||||
{ opacity: 0 },
|
||||
{ opacity: 1 }
|
||||
], {
|
||||
duration: 500,
|
||||
easing: 'ease-in-out',
|
||||
delay: delay * 1000,
|
||||
fill: 'forwards'
|
||||
});
|
||||
fadeInAnimations.push(animation);
|
||||
allAnimations.push(animation);
|
||||
return animation;
|
||||
}
|
||||
|
||||
// Swap in 动画(渐进显示)
|
||||
function createSwapInAnimation(element: Element, delay: number = 0) {
|
||||
const animation = element.animate([
|
||||
{ clipPath: 'inset(0 100% 0 0)' },
|
||||
{ clipPath: 'inset(0 0% 0 0)' }
|
||||
], {
|
||||
duration: 500,
|
||||
easing: 'ease-in-out',
|
||||
delay: delay * 1000,
|
||||
fill: 'forwards'
|
||||
});
|
||||
swapInAnimations.push(animation);
|
||||
allAnimations.push(animation);
|
||||
return animation;
|
||||
}
|
||||
|
||||
// 气泡下降动画
|
||||
function createBubbleDropAnimation(element: Element, startTop: string, startLeft: string, endTop: string, endLeft: string, delay: number = 0, duration: number = 600) {
|
||||
const animation = element.animate([
|
||||
{
|
||||
top: startTop,
|
||||
left: startLeft,
|
||||
transform: 'translate3d(-50%, -50%, 0) scale(0.1)'
|
||||
},
|
||||
{
|
||||
top: endTop,
|
||||
left: endLeft,
|
||||
transform: 'translate3d(-50%, -50%, 0) scale(1)'
|
||||
}
|
||||
], {
|
||||
duration: duration,
|
||||
easing: 'ease-out',
|
||||
delay: (delay + 1) * 1000,
|
||||
fill: 'forwards'
|
||||
});
|
||||
bubbleDropAnimations.push(animation);
|
||||
allAnimations.push(animation);
|
||||
return animation;
|
||||
}
|
||||
|
||||
// 动画相关的 refs
|
||||
const mainLogoRules = ref([
|
||||
{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }
|
||||
]);
|
||||
const selectInfoRules = ref([{ name: 'main', frame: 26, loop: 1, duration: 33, reverse: false }]);
|
||||
const startExplorationRules = ref([{ name: 'start', frame: 19, loop: 1, duration: 33, reverse: false }]);
|
||||
|
||||
|
||||
// 反向播放所有动画来淡出
|
||||
function reverseAllAnimations() {
|
||||
mainLogoRules.value[0].reverse = true;
|
||||
selectInfoRules.value[0].reverse = true;
|
||||
|
||||
allAnimations.forEach(animation => {
|
||||
// 确保动画已经完成
|
||||
if (animation.playState === 'finished') {
|
||||
animation.reverse();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 淡出所有动画(备用方案)
|
||||
function fadeOutAllAnimations() {
|
||||
const allElements = document.querySelectorAll('.fade-in, .swap-in, .scale-in-ctn, .scale-in-txt, .region-bubble');
|
||||
allElements.forEach((element) => {
|
||||
element.animate([
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 }
|
||||
], {
|
||||
duration: 1000,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露函数供外部使用
|
||||
(window as any).reverseAllAnimations = reverseAllAnimations;
|
||||
(window as any).fadeOutAllAnimations = fadeOutAllAnimations;
|
||||
|
||||
|
||||
const showSelectInfo = ref(false);
|
||||
const showSun = ref(false);
|
||||
|
||||
// Refs for DOM elements to control their styles
|
||||
const logoEl = ref<HTMLElement | null>(null);
|
||||
const mainLogoWrapperEl = ref<HTMLElement | null>(null);
|
||||
const selectInfoContainerEl = ref<HTMLElement | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
// 等待一帧确保所有元素都已渲染
|
||||
|
||||
requestAnimationFrame(async () => {
|
||||
// 初始化logo淡入动画
|
||||
const logo = document.querySelector('.logo');
|
||||
if (logo) {
|
||||
createFadeInAnimation(logo, 2);
|
||||
}
|
||||
// --- 初始化动画 ---
|
||||
|
||||
// 初始化swap-in动画
|
||||
const swapInElements = document.querySelectorAll('.swap-in');
|
||||
swapInElements.forEach((element) => {
|
||||
const htmlElement = element as HTMLElement;
|
||||
const delay = parseFloat(htmlElement.style.getPropertyValue('--base-delay')?.replace('s', '') || '0');
|
||||
createSwapInAnimation(element, delay);
|
||||
});
|
||||
// 1. Logo 淡入
|
||||
setTimeout(() => {
|
||||
if (logoEl.value) logoEl.value.style.opacity = '1';
|
||||
}, 2000);
|
||||
|
||||
// 2. 主标题和选择提示的容器淡入 (AniEle本身播放,我们控制其容器)
|
||||
|
||||
// 3. 气泡下落动画
|
||||
const regionBubbles = document.querySelectorAll<HTMLElement>('.region-bubble');
|
||||
|
||||
// 初始化气泡动画
|
||||
const regionBubbles = document.querySelectorAll('.region-bubble');
|
||||
regionBubbles.forEach((bubble, index) => {
|
||||
const region = regionsPos[index];
|
||||
const startTop = `${region.t + 50 - 200 - (100 - region.t)}%`;
|
||||
@@ -253,87 +167,136 @@ onMounted(() => {
|
||||
const endTop = `${region.t + 50}%`;
|
||||
const endLeft = `${region.l + 50}%`;
|
||||
|
||||
// 设置初始位置
|
||||
(bubble as HTMLElement).style.top = startTop;
|
||||
(bubble as HTMLElement).style.left = startLeft;
|
||||
// 设置初始离屏位置
|
||||
Object.assign(bubble.style, {
|
||||
top: startTop,
|
||||
left: startLeft,
|
||||
transform: 'translate3d(-50%, -50%, 0) scale(0.1)',
|
||||
transitionDuration: `0ms`
|
||||
});
|
||||
|
||||
createBubbleDropAnimation(bubble, startTop, startLeft, endTop, endLeft, 0, 600 + region.ad * 100);
|
||||
// 延迟触发动画
|
||||
setTimeout(() => {
|
||||
Object.assign(bubble.style, {
|
||||
top: endTop,
|
||||
left: endLeft,
|
||||
transform: 'translate3d(-50%, -50%, 0) scale(1)',
|
||||
|
||||
// 点击事件
|
||||
const pDown = () => {
|
||||
bubble.animate([
|
||||
{ transform: 'translate3d(-50%, -50%, 0) scale(1)', filter: 'brightness(1)' },
|
||||
{ transform: 'translate3d(-50%,-50%, 0) scale(0.92)', filter: 'brightness(0.9)' },
|
||||
], {
|
||||
duration: 500,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
transitionDuration: `${600 + region.ad * 100}ms`
|
||||
});
|
||||
}
|
||||
}, 1000); // 基础延迟
|
||||
|
||||
// --- 绑定事件 ---
|
||||
const pDown = () => {
|
||||
if (selectionInProgress.value) return;
|
||||
bubble.style.filter = 'brightness(0.9)';
|
||||
bubble.style.transform = 'translate3d(-50%,-50%, 0) scale(0.92)';
|
||||
};
|
||||
|
||||
const pUp = () => {
|
||||
// 设置选中的区域和店铺数据
|
||||
if (selectionInProgress.value) return;
|
||||
selectionInProgress.value = true;
|
||||
selectedBubbleEl.value = bubble;
|
||||
|
||||
selectedRegion.value = region.tt;
|
||||
selectedStores.value = getRegionStores(region.key);
|
||||
selectedStoreIndex.value = 0;
|
||||
|
||||
bubble.querySelector('span')?.animate([
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 },
|
||||
], {
|
||||
duration: 1000,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
});
|
||||
// 放大选中的气泡
|
||||
bubble.classList.add('zoomed');
|
||||
bubble.style.filter = 'brightness(1)';
|
||||
|
||||
const scaleAnimation = bubble.animate([
|
||||
{ transform: 'translate3d(-50%, -50%, 0) scale(0.92)', filter: 'brightness(0.9)' },
|
||||
{ transform: 'translate3d(-50%, -50%, 0) scale(4)', filter: 'brightness(1)', top: '53%', left: '50%' },
|
||||
], {
|
||||
duration: 1000,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
});
|
||||
// 隐藏气泡文字
|
||||
const span = bubble.querySelector('span');
|
||||
if (span) span.style.opacity = '0';
|
||||
|
||||
// 反向播放所有动画来淡出其他元素
|
||||
reverseAllAnimations();
|
||||
// 淡出其他UI元素
|
||||
if (logoEl.value) logoEl.value.style.opacity = '0';
|
||||
|
||||
mainLogoRules.value[0].reverse = true;
|
||||
selectInfoRules.value[0].reverse = true;
|
||||
|
||||
// 淡出其他气泡
|
||||
regionBubbles.forEach((otherBubble, otherIndex) => {
|
||||
if (otherIndex == index) return
|
||||
otherBubble.animate([
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 },
|
||||
], {
|
||||
duration: 1000,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards'
|
||||
});
|
||||
regionBubbles.forEach(otherBubble => {
|
||||
if (otherBubble !== bubble) {
|
||||
otherBubble.style.transform = 'translate3d(-50%, -50%, 0) scale(0)';
|
||||
otherBubble.style.opacity = '0';
|
||||
} else {
|
||||
otherBubble.style.transitionTimingFunction = 'cubic-bezier(0.71, 0.14, 0.27, 1.55)';
|
||||
setTimeout(() => {
|
||||
otherBubble.style.transitionTimingFunction = 'ease';
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// 动画完成后显示选择界面
|
||||
scaleAnimation.addEventListener('finish', () => {
|
||||
setTimeout(() => {
|
||||
showStoreSelector.value = true;
|
||||
|
||||
// 初始化 Intersection Observer
|
||||
initializeStoreSelector();
|
||||
});
|
||||
|
||||
bubble.removeEventListener('pointerdown', pDown);
|
||||
}
|
||||
}, 1000); // 等待放大动画完成
|
||||
};
|
||||
|
||||
bubble.addEventListener('pointerdown', pDown);
|
||||
bubble.addEventListener('pointerup', pUp, { once: true });
|
||||
bubble.addEventListener('pointerup', pUp);
|
||||
});
|
||||
|
||||
await wait(1300);
|
||||
showSelectInfo.value = true;
|
||||
// 等待showSelectInfo渲染后,设置其容器淡入
|
||||
await wait(10);
|
||||
if (selectInfoContainerEl.value) selectInfoContainerEl.value.style.opacity = '1';
|
||||
|
||||
await wait(300);
|
||||
showSun.value = true;
|
||||
});
|
||||
});
|
||||
|
||||
// 组件卸载时清理 observer
|
||||
|
||||
function goBackToRegionSelection() {
|
||||
if (!selectedBubbleEl.value) return;
|
||||
selectionInProgress.value = true; // 加锁防止重复操作
|
||||
|
||||
mainLogoRules.value[0].reverse = false;
|
||||
selectInfoRules.value[0].reverse = false;
|
||||
|
||||
// 1. 隐藏店铺选择器
|
||||
showStoreSelector.value = false;
|
||||
cleanupObserver();
|
||||
|
||||
const bubble = selectedBubbleEl.value;
|
||||
|
||||
// 2. 将放大的气泡恢复原状
|
||||
bubble.classList.remove('zoomed');
|
||||
|
||||
// 3. 恢复气泡文字
|
||||
const span = bubble.querySelector('span');
|
||||
if (span) span.style.opacity = '1';
|
||||
|
||||
// 4. 恢复其他UI元素
|
||||
if (logoEl.value) logoEl.value.style.opacity = '1';
|
||||
if (mainLogoWrapperEl.value) mainLogoWrapperEl.value.style.opacity = '1';
|
||||
if (selectInfoContainerEl.value) selectInfoContainerEl.value.style.opacity = '1';
|
||||
|
||||
// 5. 恢复其他气泡
|
||||
const allBubbles = document.querySelectorAll<HTMLElement>('.region-bubble');
|
||||
allBubbles.forEach(otherBubble => {
|
||||
if (otherBubble !== bubble) {
|
||||
otherBubble.style.opacity = '1';
|
||||
otherBubble.style.transform = 'translate3d(-50%, -50%, 0) scale(1)';
|
||||
}
|
||||
});
|
||||
|
||||
// 6. 动画结束后重置状态
|
||||
setTimeout(() => {
|
||||
selectedRegion.value = '';
|
||||
selectedStores.value = [];
|
||||
selectedStoreIndex.value = 0;
|
||||
selectedBubbleEl.value = null;
|
||||
selectionInProgress.value = false; // 解锁
|
||||
}, 1000); // 等待动画完成
|
||||
}
|
||||
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanupObserver();
|
||||
});
|
||||
@@ -342,40 +305,41 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<section class="p1">
|
||||
<img :src="assets.标准logo" alt="" class="logo" style="top: 6%;left: 8%;width: 12%;opacity: 0;">
|
||||
<img :src="assets.p1.蓝天" alt="" class="bg">
|
||||
<img :src="assets.标准logo" alt="" class="logo" ref="logoEl">
|
||||
|
||||
<AniEle :url="assets.ani.主标出现" :width="1015" :height="336" :rules="mainLogoRules"
|
||||
style="top: 10%;width: 84%;left: 8%;" />
|
||||
<div class="main-logo-wrapper" ref="mainLogoWrapperEl">
|
||||
<AniEle :url="assets.ani.主标出现" :width="1015" :height="336"
|
||||
:rules="mainLogoRules" style="top: 10%;width: 84%;left: 8%;" />
|
||||
</div>
|
||||
|
||||
<div class="sun-warp" v-if="showSun">
|
||||
<AniEle :url="assets.ani.P1太阳总" :width="1000" :height="1000"
|
||||
:rules="[{ name: '下落', frame: 69, loop: 1, duration: 33 }, { name: '循环', frame: 75, loop: 0, duration: 33, reverse: false }]" />
|
||||
</div>
|
||||
|
||||
<!-- <img :src="assets.p1.太阳" alt="" style="top: 30%;left: 5%;width: 26%;"> -->
|
||||
<div style="width: 86vw;height: 86vw;top: 47%; left: 50%;transform: translate3d(-50%, -50%, 0); z-index: -1;">
|
||||
<div v-for="i in regionsPos" :style="{
|
||||
<div
|
||||
style="width: 86vw;height: 86vw;top: 47%; left: 50%;transform: translate3d(-50%, -50%, 0); z-index: -1;">
|
||||
<div v-for="i in regionsPos" :key="i.key" :style="{
|
||||
width: i.w + '%',
|
||||
height: i.h + '%',
|
||||
transform: 'translate3d(-50%, -50%, 0) scale(0)',
|
||||
}" class="region-bubble">
|
||||
|
||||
<img :src="i.src" alt="" style="height: 100%;width: 100%;">
|
||||
<img :src="i.src" alt="" style="height: 100%;width: 100%;"
|
||||
draggable="false">
|
||||
<span
|
||||
style="font-size: 4.5vw;left: 50%;transform: translate(-50%, -50%);font-weight: 400;width: fit-content;"
|
||||
:style="{ color: i.tc || '#000', top: (i.trt ?? 0) + 50 + '%' }">
|
||||
*{{ i.tt }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="inset: 0;width: 100%;top: 52%;pointer-events: none;" v-if="showSelectInfo">
|
||||
<AniEle :url="assets.ani.点击选择阵营" :width="1179" :height="2462" ref="click-select-ani"
|
||||
<div class="select-info-container" ref="selectInfoContainerEl"
|
||||
v-if="showSelectInfo">
|
||||
<AniEle :url="assets.ani.点击选择阵营" :width="1179" :height="2462"
|
||||
:rules="selectInfoRules" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 店铺选择界面 -->
|
||||
<div v-if="showStoreSelector" class="store-selector">
|
||||
<div class="store-selector-content">
|
||||
@@ -383,15 +347,16 @@ onUnmounted(() => {
|
||||
|
||||
<div class="store-list-container">
|
||||
<div class="store-list relative">
|
||||
<!-- 顶部填充,确保第一个元素能滚动到中间 -->
|
||||
<div class="scroll-padding relative"></div>
|
||||
|
||||
<div v-for="(store, index) in selectedStores" :key="store.店铺号" class="store-item relative"
|
||||
:class="{ active: index === selectedStoreIndex }" @click="selectStore(index)">
|
||||
<div class="store-name relative">{{ store.店铺 }}</div>
|
||||
<div v-for="(store, index) in selectedStores"
|
||||
:key="store.店铺号" class="store-item relative"
|
||||
:class="{ active: index === selectedStoreIndex }"
|
||||
@click="selectStore(index)">
|
||||
<div class="store-name relative">{{ store.店铺 }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部填充,确保最后一个元素能滚动到中间 -->
|
||||
<div class="scroll-padding relative"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -402,16 +367,80 @@ onUnmounted(() => {
|
||||
<input type="text" class="name-input" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
<img class="input-your-name-notice"
|
||||
src="../assets/p1/请填写你的名字.png" alt=""
|
||||
v-if="noticeToInputName">
|
||||
|
||||
<AniEle :url="assets.ani.开始探索" :width="548" :height="143" :rules="startExplorationRules" class="start-button relative"
|
||||
<AniEle :url="assets.ani.开始探索" :width="548" :height="143"
|
||||
:rules="startExplorationRules" class="start-button"
|
||||
@click="startExploration">
|
||||
</AniEle>
|
||||
</div>
|
||||
|
||||
<img src="../assets/返回按钮.png" alt="返回"
|
||||
@click="goBackToRegionSelection" class="back-button" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* --- Base Styles and Transitions --- */
|
||||
.logo {
|
||||
top: 6%;
|
||||
left: 8%;
|
||||
width: 12%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.main-logo-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.select-info-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
top: 52%;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.region-bubble {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
// transition-duration is set dynamically in script
|
||||
transition-property: top, left, transform, opacity;
|
||||
transition-timing-function: ease;
|
||||
will-change: top, left, transform, opacity;
|
||||
|
||||
&.zoomed {
|
||||
top: 53% !important;
|
||||
left: 50% !important;
|
||||
transform: translate3d(-50%, -50%, 0) scale(4) !important;
|
||||
transition-duration: 1000ms !important;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 4.5vw;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: 400;
|
||||
width: fit-content;
|
||||
position: absolute;
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --- Other Styles --- */
|
||||
|
||||
@keyframes sun-drop {
|
||||
0% {
|
||||
transform: translateY(-90vw);
|
||||
@@ -431,95 +460,45 @@ onUnmounted(() => {
|
||||
animation: sun-drop 1s cubic-bezier(0.54, 0.46, 0.44, 1.28) forwards;
|
||||
}
|
||||
|
||||
@mixin center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag {
|
||||
width: max-content;
|
||||
padding: 0.5vw .8vw;
|
||||
}
|
||||
|
||||
.green {
|
||||
@include center;
|
||||
background-color: #85AF4C;
|
||||
color: transparent;
|
||||
font-weight: bold;
|
||||
width: max-content;
|
||||
|
||||
&>span,
|
||||
&>svg {
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.fake {
|
||||
background-color: transparent;
|
||||
color: #FFF;
|
||||
|
||||
&>span,
|
||||
&>svg {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.white {
|
||||
@include center;
|
||||
background-color: #FFF;
|
||||
font-size: 5vw;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.black {
|
||||
@include center;
|
||||
background-color: #000;
|
||||
font-size: 11vw;
|
||||
font-weight: bold;
|
||||
color: transparent;
|
||||
|
||||
&.fake {
|
||||
background-color: transparent;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.region-bubble {
|
||||
position: absolute;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
span,
|
||||
div {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
div,
|
||||
img {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
z-index: -11;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 店铺选择界面样式 */
|
||||
.store-selector {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
z-index: 10;
|
||||
animation: fade-in 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 16vw;
|
||||
left: 8vw;
|
||||
width: 8vw;
|
||||
animation: fade-in 0.3s ease-in-out;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
z-index: 111;
|
||||
}
|
||||
|
||||
.store-selector-content {
|
||||
top: 15%;
|
||||
top: 10%;
|
||||
left: 5%;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
@@ -535,19 +514,11 @@ img {
|
||||
|
||||
.store-list-container {
|
||||
margin-bottom: 12vw;
|
||||
height: 40vh;
|
||||
height: 35vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
mask-image: linear-gradient(to bottom,
|
||||
transparent 0%,
|
||||
black 15%,
|
||||
black 85%,
|
||||
transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom,
|
||||
transparent 0%,
|
||||
black 15%,
|
||||
black 85%,
|
||||
transparent 100%);
|
||||
mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 15%, black 85%, transparent 100%);
|
||||
}
|
||||
|
||||
.store-list {
|
||||
@@ -556,11 +527,17 @@ img {
|
||||
scroll-behavior: smooth;
|
||||
scroll-snap-type: y mandatory;
|
||||
padding: 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scroll-padding {
|
||||
height: calc(20vh - 4vw);
|
||||
/* 容器高度的一半减去项目高度的一半 */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -570,7 +547,6 @@ img {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 8vw;
|
||||
scroll-snap-align: center;
|
||||
transition: all 0.3s ease;
|
||||
@@ -587,29 +563,32 @@ img {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.center-indicator {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 90%;
|
||||
height: 10vw;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
|
||||
.name-input-section {
|
||||
margin-bottom: 8vw;
|
||||
margin-bottom: 2vw;
|
||||
|
||||
.title {
|
||||
font-size: 5vw;
|
||||
color: #333;
|
||||
margin-bottom: 4vw;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.name-input-section .title {
|
||||
font-size: 5vw;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4vw;
|
||||
font-weight: normal;
|
||||
@keyframes pop-up {
|
||||
from {
|
||||
scale: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.input-your-name-notice {
|
||||
width: 40%;
|
||||
left: 30%;
|
||||
animation: pop-up 0.3s ease-out forwards;
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
@@ -622,47 +601,22 @@ img {
|
||||
width: 100%;
|
||||
padding: 2vw 0;
|
||||
font-size: 4.5vw;
|
||||
background: transparent;
|
||||
background: transparent url('../assets/p1/输入框.png') center / contain no-repeat;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
background: url('../assets/p1/输入框.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4vw;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
width: 40vw;
|
||||
left: 30%;
|
||||
cursor: pointer;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
transition: transform 0.1s ease;
|
||||
bottom: -30%;
|
||||
|
||||
.start-button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.start-button .arrow {
|
||||
font-size: 5vw;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
.store-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.store-list {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user