topfans/frontend/pages/components/WelcomeAnimation.vue
2026-04-07 23:08:49 +08:00

1351 lines
34 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="welcome-mask" @click="onBgClick">
<text class="header-text">SKIP</text>
<!-- 背景层 -->
<view class="background-layer">
<!-- 场景1: 视频图.mp4 -->
<view v-if="currentScene === 0" class="scene1-wrapper">
<video class="bg-media" src="/static/animation/animation1.mp4" loop autoplay muted object-fit="fill"
:show-center-play-btn="false" :show-play-btn="false" :enable-progress-gesture="false"
:controls="false"></video>
<!-- 场景1对话框 - 位置动态配置 -->
<view class="scene1-dialog" :style="{ left: currentDialogPos.left, top: currentDialogPos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg" src="/static/animation/dialog-monster/dialog.png" mode="aspectFit" />
<view class="dialog-text-wrapper-vertical">
<text class="dialog-text-vertical">{{ currentText }}</text>
</view>
</view>
</view>
<!-- 场景2: 视频轮播 图3,图4,图5 (三个独立区域) -->
<view v-else-if="currentScene === 1" class="scene2-layout">
<!-- 图3: 底层视频 (始终显示) -->
<view class="scene2-item scene2-item-1">
<video class="scene2-video" src="/static/animation/animation3.mp4" loop autoplay muted
object-fit="fill" :show-center-play-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :controls="false"></video>
</view>
<!-- 图4: 上层视频 (scene2SubPhase >= 1 时显示) -->
<view v-if="scene2SubPhase >= 1" class="scene2-item scene2-item-2 visible">
<video class="scene2-video scene2-video-right" src="/static/animation/animation4.mp4" loop autoplay
muted object-fit="fill" :show-center-play-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :controls="false"></video>
</view>
<!-- 图4对话框: 覆盖在图3上面 (scene2SubPhase >= 1 时显示) -->
<view v-if="scene2SubPhase >= 1" class="scene2-dialog-on-video3"
:style="{ left: scene1DialogOnVideo3Pos.left, top: scene1DialogOnVideo3Pos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval" src="/static/animation/dialog-monster/oval-dialog1.png"
mode="aspectFit" />
<view class="dialog-text-wrapper-vertical dialog-text-wrapper-vertical-on-video3">
<text class="dialog-text-vertical">{{ scene2Video4Text }}</text>
</view>
</view>
<!-- 图5: 视频 (scene2SubPhase >= 2 时显示) -->
<view v-if="scene2SubPhase >= 2" class="scene2-item scene2-item-3 visible">
<view class="scene2-video-crop">
<video class="scene2-video scene2-video-crop-inner" src="/static/animation/animation5.mp4" loop
autoplay muted object-fit="fill" :show-center-play-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :controls="false"></video>
</view>
</view>
<!-- 图5对话框: 覆盖在图4上面 (scene2SubPhase >= 2 时显示) -->
<view v-if="scene2SubPhase >= 2" class="scene2-dialog-on-video4"
:style="{ left: scene2DialogOnVideo5Pos.left, top: scene2DialogOnVideo5Pos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval1" src="/static/animation/dialog-monster/dialog2.png"
mode="aspectFit" />
<view class="dialog-text-wrapper-vertical">
<text class="dialog-text-vertical">{{ scene2Video5Text }}</text>
</view>
</view>
</view>
<!-- 场景3: 图2,图6 (上下布局) -->
<view v-else-if="currentScene === 2" class="scene3-layout">
<view class="scene3-top">
<video class="scene3-video" src="/static/animation/animation2.mp4" loop autoplay muted
object-fit="fill" :show-center-play-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :controls="false"></video>
<!-- 上部分对话框 -->
<view class="scene1-dialog" :style="{ left: scene3DialogTopPos.left, top: scene3DialogTopPos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval-1" src="/static/animation/dialog-monster/dialog2.png"
mode="aspectFit" />
<view class="dialog-text-wrapper-vertical dialog-text-wrapper-vertical-on-video4">
<text class="dialog-text-vertical">{{ currentText }}</text>
</view>
</view>
</view>
<view class="scene3-bottom" v-if="scene3SubPhase >= 1">
<video class="scene3-video" src="/static/animation/animation6.mp4" loop autoplay muted
object-fit="fill" :show-center-play-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :controls="false"></video>
<!-- 下部分对话框 -->
<view class="scene1-dialog"
:style="{ left: scene3DialogBottomPos.left, top: scene3DialogBottomPos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval-2"
src="/static/animation/dialog-monster/rounded-corner-dialog1.png" mode="aspectFit" />
<view class="dialog-text-wrapper-vertical dialog-text-wrapper-vertical-on-video5">
<text class="dialog-text-vertical">{{ scene3Video6Text }}</text>
</view>
</view>
</view>
</view>
<!-- 场景4: 视频轮播 图7,图8 (两个独立区域) -->
<view v-else-if="currentScene === 3" class="scene4-layout">
<!-- 图7: 有对话框 -->
<view class="scene4-item scene4-item-1">
<video class="scene4-video" src="/static/animation/animation7.mp4" loop autoplay muted
:show-center-play-btn="false" :show-play-btn="false" :enable-progress-gesture="false"
:controls="false"></video>
<view class="scene1-dialog" :style="{ left: scene4Dialog7Pos.left, top: scene4Dialog7Pos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval-3"
src="/static/animation/dialog-monster/rounded-corner-dialog1.png" mode="aspectFit" />
<view class="dialog-text-wrapper-vertical dialog-text-wrapper-vertical-on-video6">
<text class="dialog-text-vertical dialog-text-vertical-on-video5">{{ scene4Video7Text
}}</text>
</view>
</view>
</view>
<!-- 图8: 有对话框 -->
<view class="scene4-item scene4-item-2" v-if="scene4SubPhase >= 1">
<video class="scene4-video" src="/static/animation/animation8.mp4" loop autoplay muted
object-fit="fill" :show-center-play-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :controls="false"></video>
<view class="scene1-dialog" :style="{ left: scene4Dialog8Pos.left, top: scene4Dialog8Pos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval-4" src="/static/animation/dialog-monster/dialog3.png"
mode="aspectFit" />
<view class="dialog-text-wrapper-vertical dialog-text-wrapper-vertical-on-video7">
<text class="dialog-text-vertical">{{ scene4Video8Text }}</text>
</view>
</view>
<!-- 小怪兽图片 -->
<image class="scene4-monster" :style="{ left: scene4MonsterPos.left, top: scene4MonsterPos.top }"
src="/static/animation/dialog-monster/monster.png" mode="aspectFit" />
</view>
</view>
<!-- 场景5: 选择明星页面 -->
<view v-else-if="currentScene === 4" class="scene-role">
<SelectRole :is-welcome="true" @confirm="onSelectRoleConfirm" />
</view>
<!-- 场景6: 视频图9.mp4 -->
<view v-else-if="currentScene === 5" class="scene6-wrapper">
<view class="scene6-bg">
<video class="bg-media" src="/static/animation/animation9.mp4" loop autoplay muted object-fit="fill"
:show-center-play-btn="false" :show-play-btn="false" :enable-progress-gesture="false"
:controls="false"></video>
<view class="scene1-dialog" :style="{ left: scene6DialogPos.left, top: scene6DialogPos.top }"
:class="{ visible: dialogVisible }">
<image class="dialog-bg dialog-bg-oval-5" src="/static/animation/dialog-monster/dialog3.png"
mode="aspectFit" />
<view class="dialog-text-wrapper-vertical dialog-text-wrapper-vertical-on-video8">
<text class="dialog-text-vertical dialog-text-vertical-on-video5">{{ scene6Video9Text }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- Topfans文字 + 箭头 (仅场景1-4) -->
<view v-if="currentScene <= 3" class="topfans-arrow-wrapper" :class="{ visible: contentVisible }"
@click.stop="nextScene">
<text class="topfans-text">TOPFANS</text>
<text class="arrow-icon"></text>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
import { useStore } from "vuex";
import SelectRole from "../profile/selectRole.vue";
const store = useStore();
const emit = defineEmits(["complete", "skip"]);
// 固定资源
const monsterSrc = "/static/animation/dialog-monster/monster.png";
// 每场景的文案
const sceneTexts = [
"这些限量款徽章都没舍得用过……", // 场景1
"还可以铸造专属藏品哦~", // 场景3
"这些都是我的宝贝,难道都会悄悄褪色吗?", // 场景4
"选择你喜欢的明星吧!", // 场景5
];
// 图4和图5对话框的独立文字
const scene2Video4Text = ref("啊啊啊这张小卡怎么褪色了555");
const scene2Video5Text = ref("当时可是排好久才买到的。");
// 场景3图6的对话框文字
const scene3Video6Text = ref("当然不会!角角我能让这些铸造成永久的藏品。");
// 场景4图7的对话框文字
const scene4Video7Text = ref("是真的吗,我要怎么做?");
// 场景4图8的对话框文字
const scene4Video8Text = ref("只需要告诉我Ta的名字我就能带你去Ta的TopFans。");
// 场景6图9的对话框文字
const scene6Video9Text = ref("好,传送门打开了,我们出发吧");
// 对话框位置配置 (每个场景可配置)
const dialogPosConfig = ref({
scene0: { leftRatio: 0.35, topRatio: 0.2 },
// 场景3: 图2对话框 (上部分)
scene3_top: { leftRatio: 0.35, topRatio: 0.135 },
// 场景3: 图6对话框 (下部分)
scene3_bottom: { leftRatio: -0.08, topRatio: 0.06 },
// 场景4: 图7对话框 (上部分)
scene4_top: { leftRatio: 0.4, topRatio: -0.06 },
// 场景4: 图8对话框 (下部分)
scene4_bottom: { leftRatio: 0.035, topRatio: 0.2 },
// 场景4: 小怪兽
scene4Monster: { leftRatio: 0, topRatio: 0.15 },
// 图4对话框覆盖在图3上面
scene1DialogOnVideo3: { leftRatio: 0.11, topRatio: 0.30 },
// 图5对话框
scene2DialogOnVideo5: { leftRatio: 0.4, topRatio: 0.7 },
// 场景6: 图9对话框
scene6: { leftRatio: 0.15, topRatio: 0.15 },
});
// 当前对话框位置
const currentDialogPos = ref({
left: "0",
top: "0"
});
// 图4对话框位置
const scene1DialogOnVideo3Pos = ref({
left: "0",
top: "0"
});
// 图5对话框位置
const scene2DialogOnVideo5Pos = ref({
left: "0",
top: "0"
});
// 场景3图2/图6对话框位置
const scene3DialogTopPos = ref({
left: "0",
top: "0"
});
const scene3DialogBottomPos = ref({
left: "0",
top: "0"
});
// 场景4对话框位置
const scene4Dialog7Pos = ref({
left: "0",
top: "0"
});
const scene4Dialog8Pos = ref({
left: "0",
top: "0"
});
// 场景4小怪兽位置
const scene4MonsterPos = ref({
left: "0",
top: "0"
});
// 场景6对话框位置
const scene6DialogPos = ref({
left: "0",
top: "0"
});
// 通用:动态计算对话框位置
const calculateDialogPos = (sceneIndex) => {
const systemInfo = uni.getSystemInfoSync();
const screenWidth = systemInfo.windowWidth;
const screenHeight = systemInfo.windowHeight;
// 计算屏幕宽高比
const ratio = screenWidth / screenHeight;
// 获取当前场景的配置
const configKey = `scene${sceneIndex}`;
const config = dialogPosConfig.value[configKey] || dialogPosConfig.value.scene0;
// 根据屏幕比例动态调整
const leftBase = config.leftRatio;
const leftAdjust = ratio > 0.5 ? (ratio - 0.5) * 0.3 : 0;
currentDialogPos.value = {
left: Math.round(screenWidth * (leftBase + leftAdjust)) + "px",
top: Math.round(screenHeight * config.topRatio) + "px"
};
};
// 计算图4对话框位置覆盖在图3上面
const calculateScene1DialogOnVideo3Pos = () => {
const systemInfo = uni.getSystemInfoSync();
const screenWidth = systemInfo.windowWidth;
const screenHeight = systemInfo.windowHeight;
const config = dialogPosConfig.value.scene1DialogOnVideo3;
const ratio = screenWidth / screenHeight;
const leftAdjust = ratio > 0.5 ? (ratio - 0.5) * 0.3 : 0;
scene1DialogOnVideo3Pos.value = {
left: Math.round(screenWidth * (config.leftRatio + leftAdjust)) + "px",
top: Math.round(screenHeight * config.topRatio) + "px"
};
};
// 计算图5对话框位置
const calculateScene2DialogOnVideo5Pos = () => {
const systemInfo = uni.getSystemInfoSync();
const screenWidth = systemInfo.windowWidth;
const screenHeight = systemInfo.windowHeight;
const config = dialogPosConfig.value.scene2DialogOnVideo5;
const ratio = screenWidth / screenHeight;
const leftAdjust = ratio > 0.5 ? (ratio - 0.5) * 0.3 : 0;
scene2DialogOnVideo5Pos.value = {
left: Math.round(screenWidth * (config.leftRatio + leftAdjust)) + "px",
top: Math.round(screenHeight * config.topRatio) + "px"
};
};
// 计算场景3图2/图6对话框位置
const calculateScene3DialogPos = () => {
const systemInfo = uni.getSystemInfoSync();
const screenWidth = systemInfo.windowWidth;
const screenHeight = systemInfo.windowHeight;
// 上部分对话框
const configTop = dialogPosConfig.value.scene3_top;
const ratio = screenWidth / screenHeight;
const leftAdjust = ratio > 0.5 ? (ratio - 0.5) * 0.3 : 0;
scene3DialogTopPos.value = {
left: Math.round(screenWidth * (configTop.leftRatio + leftAdjust)) + "px",
top: Math.round(screenHeight * configTop.topRatio) + "px"
};
// 下部分对话框
const configBottom = dialogPosConfig.value.scene3_bottom;
scene3DialogBottomPos.value = {
left: Math.round(screenWidth * (configBottom.leftRatio + leftAdjust)) + "px",
top: Math.round(screenHeight * configBottom.topRatio) + "px"
};
};
// 计算场景4图7/图8对话框位置
const calculateScene4DialogPos = () => {
const systemInfo = uni.getSystemInfoSync();
const screenWidth = systemInfo.windowWidth;
const screenHeight = systemInfo.windowHeight;
const ratio = screenWidth / screenHeight;
const leftAdjust = ratio > 0.5 ? (ratio - 0.5) * 0.3 : 0;
// 上部分对话框
const configTop = dialogPosConfig.value.scene4_top;
scene4Dialog7Pos.value = {
left: Math.round(screenWidth * (configTop.leftRatio + leftAdjust)) + "px",
top: Math.round(screenHeight * configTop.topRatio) + "px"
};
// 下部分对话框
const configBottom = dialogPosConfig.value.scene4_bottom;
scene4Dialog8Pos.value = {
left: Math.round(screenWidth * (configBottom.leftRatio + leftAdjust)) + "px",
top: Math.round(screenHeight * configBottom.topRatio) + "px"
};
// 小怪兽位置 - 在图8对话框右边
const configMonster = dialogPosConfig.value.scene4Monster;
scene4MonsterPos.value = {
left: 0 + "px",
top: Math.round(screenHeight * configMonster.topRatio) + "px"
};
};
// 计算场景6图9对话框位置
const calculateScene6DialogPos = () => {
const systemInfo = uni.getSystemInfoSync();
const screenWidth = systemInfo.windowWidth;
const screenHeight = systemInfo.windowHeight;
const config = dialogPosConfig.value.scene6;
scene6DialogPos.value = {
left: Math.round(screenWidth * config.leftRatio) + "px",
top: Math.round(screenHeight * config.topRatio) + "px"
};
};
// 响应式状态
const currentScene = ref(0);
const contentVisible = ref(false);
const dialogVisible = ref(false);
const monsterVisible = ref(false);
// 场景2的子阶段0=只显示图3, 1=显示图3+图4, 2=显示图3+图4+图5
const scene2SubPhase = ref(0);
// 场景3的子阶段0=只显示图2, 1=显示图2+图6
const scene3SubPhase = ref(0);
// 场景4的子阶段0=只显示图7, 1=显示图7+图8
const scene4SubPhase = ref(0);
const currentText = computed(() => sceneTexts[currentScene.value]);
// 选择明星确认后调用注册接口
const onSelectRoleConfirm = async (star) => {
console.log("[WelcomeAnimation] onSelectRoleConfirm called, star:", star);
try {
// 获取临时存储的注册信息
const mobile = uni.getStorageSync("temp_register_mobile");
const password = uni.getStorageSync("temp_register_password");
const nickname = uni.getStorageSync("temp_register_nickname");
const star_id = star.star_id;
console.log("[WelcomeAnimation] register info:", {
mobile,
password,
nickname,
star_id,
});
if (!mobile || !password || !nickname || !star_id) {
console.log("[WelcomeAnimation] 注册信息不完整");
uni.showToast({
title: "注册信息不完整",
icon: "none",
});
// 清除已选择的注册信息,重新注册
uni.removeStorageSync("temp_register_mobile");
uni.removeStorageSync("temp_register_password");
uni.removeStorageSync("temp_register_nickname");
// 返回注册页面重新注册
uni.navigateTo({
url: '/pages/register/register'
});
return;
}
// 显示加载提示
uni.showLoading({
title: "注册中...",
mask: true,
});
// 调用注册 API
console.log("[WelcomeAnimation] 开始调用注册API");
await store.dispatch("user/register", {
mobile,
password,
star_id,
nickname,
});
console.log("[WelcomeAnimation] 注册API调用成功");
uni.hideLoading();
// 清除临时数据
uni.removeStorageSync("temp_register_mobile");
uni.removeStorageSync("temp_register_password");
uni.removeStorageSync("temp_register_nickname");
// 跳转到场景6
console.log("[WelcomeAnimation] 跳转到场景6");
currentScene.value = 5;
// 2秒后自动结束
setTimeout(() => {
handleFinish();
}, 2000);
} catch (error) {
console.log("[WelcomeAnimation] 注册失败:", error);
uni.hideLoading();
uni.showToast({
title: error.message || "注册失败,请重试",
icon: "none",
});
// 清除已选择的注册信息,重新注册
uni.removeStorageSync("temp_register_mobile");
uni.removeStorageSync("temp_register_password");
uni.removeStorageSync("temp_register_nickname");
// 返回注册页面重新注册
uni.navigateTo({
url: '/pages/register/register'
});
}
};
// 点击背景显示文字和箭头处理场景2的子阶段切换
const onBgClick = (e) => {
// 阻止事件冒泡,避免触发背景点击
e && e.stopPropagation && e.stopPropagation();
// 场景2处理子阶段切换
if (currentScene.value === 1) {
if (scene2SubPhase.value === 0) {
// 图3显示时点击立即进入下一阶段显示图4
scene2SubPhase.value = 1;
} else if (scene2SubPhase.value === 1) {
// 图4显示时点击立即进入下一阶段显示图5
scene2SubPhase.value = 2;
} else if (scene2SubPhase.value === 2) {
// 图5显示时点击显示Topfans文字+箭头
if (!contentVisible.value) {
contentVisible.value = true;
}
}
return;
}
// 场景3处理子阶段切换
if (currentScene.value === 2) {
if (scene3SubPhase.value === 0) {
// 图2显示时点击立即进入下一阶段显示图6
scene3SubPhase.value = 1;
} else if (scene3SubPhase.value === 1) {
// 图6显示时点击显示Topfans文字+箭头
if (!contentVisible.value) {
contentVisible.value = true;
}
}
return;
}
// 场景4处理子阶段切换
if (currentScene.value === 3) {
if (scene4SubPhase.value === 0) {
// 图7显示时点击立即进入下一阶段显示图8
scene4SubPhase.value = 1;
} else if (scene4SubPhase.value === 1) {
// 图8显示时点击显示Topfans文字+箭头
if (!contentVisible.value) {
contentVisible.value = true;
}
}
return;
}
// 其他场景:点击显示文字和箭头
if (currentScene.value <= 3 && !contentVisible.value) {
contentVisible.value = true;
}
};
let carouselTimer = null;
let autoShowTimer = null;
// 切换到下一个场景
const nextScene = () => {
if (currentScene.value === 0) {
// 场景1: 切换到场景2 (图3、图4、图5 上中下)
currentScene.value = 1;
resetSceneAnimation();
} else if (currentScene.value === 1) {
// 场景2: 切换到场景3 (图2、图6 上下)
currentScene.value = 2;
resetSceneAnimation();
} else if (currentScene.value === 2) {
// 场景3: 切换到场景4 (图7、图8 上下)
currentScene.value = 3;
resetSceneAnimation();
} else if (currentScene.value === 3) {
// 场景4: 切换到场景5选择明星
currentScene.value = 4;
resetSceneAnimation();
} else if (currentScene.value === 4) {
// 场景5: 选择明星页面,由组件触发确认事件
// 不做任何操作,等待用户选择明星后触发 onSelectRoleConfirm
} else {
handleFinish();
}
};
// 跳过
const handleSkip = () => {
cleanup();
emit("skip");
};
// 完成
const handleFinish = () => {
cleanup();
uni.setStorageSync("has_seen_welcome", true);
emit("complete");
};
// 重置场景动画
const resetSceneAnimation = () => {
// 清除之前的自动显示定时器
if (autoShowTimer) {
clearTimeout(autoShowTimer);
autoShowTimer = null;
}
contentVisible.value = false;
dialogVisible.value = false;
monsterVisible.value = false;
// 场景2子阶段重置
scene2SubPhase.value = 0;
// 切换场景时只自动显示对话框和小怪兽,文字和箭头需要点击显示
setTimeout(() => {
dialogVisible.value = true;
}, 300);
setTimeout(() => {
monsterVisible.value = true;
}, 800);
// 切换场景时重新计算对话框位置
calculateDialogPos(currentScene.value);
// 场景1: 5秒后自动显示文字和箭头
if (currentScene.value === 0) {
autoShowTimer = setTimeout(() => {
contentVisible.value = true;
}, 4800);
}
// 场景2: 自动逐步显示(只在用户没有手动点击时触发)
if (currentScene.value === 1) {
// 图3显示后4秒自动进入下一阶段显示图4
autoShowTimer = setTimeout(() => {
if (scene2SubPhase.value === 0) {
scene2SubPhase.value = 1;
}
// 图4显示后4秒自动进入下一阶段显示图5
autoShowTimer = setTimeout(() => {
if (scene2SubPhase.value === 1) {
scene2SubPhase.value = 2;
}
// 图5显示后10秒自动显示Topfans文字+箭头
autoShowTimer = setTimeout(() => {
if (!contentVisible.value) {
contentVisible.value = true;
}
}, 9800);
}, 3800);
}, 3800);
}
// 场景3: 自动逐步显示
if (currentScene.value === 2) {
// 图2显示后4秒自动进入下一阶段显示图6
autoShowTimer = setTimeout(() => {
if (scene3SubPhase.value === 0) {
scene3SubPhase.value = 1;
}
// 图6显示后10秒自动显示Topfans文字+箭头
autoShowTimer = setTimeout(() => {
if (!contentVisible.value) {
contentVisible.value = true;
}
}, 2800);
}, 8800);
}
// 场景4: 自动逐步显示
if (currentScene.value === 3) {
// 图7显示后4秒自动进入下一阶段显示图8
autoShowTimer = setTimeout(() => {
if (scene4SubPhase.value === 0) {
scene4SubPhase.value = 1;
}
// 图8显示后10秒自动显示Topfans文字+箭头
autoShowTimer = setTimeout(() => {
if (!contentVisible.value) {
contentVisible.value = true;
}
}, 7800);
}, 3800);
}
};
// 清理定时器
const cleanup = () => {
if (carouselTimer) {
clearInterval(carouselTimer);
carouselTimer = null;
}
if (autoShowTimer) {
clearTimeout(autoShowTimer);
autoShowTimer = null;
}
};
// 启动视频轮播仅场景1、5、6需要自动切换
const startCarousel = () => {
// 场景2-4不需要轮播每个场景只显示一次
carouselTimer = setInterval(() => {
// 这里可以留作未来扩展目前场景2-4不需要自动切换
}, 3000);
};
onMounted(() => {
// 动态计算当前场景对话框位置
calculateDialogPos(currentScene.value);
// 计算图4对话框位置
calculateScene1DialogOnVideo3Pos();
calculateScene2DialogOnVideo5Pos();
calculateScene3DialogPos();
calculateScene4DialogPos();
calculateScene6DialogPos();
// 初始动画 - 对话框和小怪兽自动显示,文字和箭头需要点击显示
setTimeout(() => {
dialogVisible.value = true;
}, 500);
setTimeout(() => {
monsterVisible.value = true;
}, 1500);
// 场景1: 5秒后自动显示 Topfans文字+箭头
if (currentScene.value === 0) {
autoShowTimer = setTimeout(() => {
contentVisible.value = true;
}, 5000);
}
// 启动轮播
startCarousel();
});
onUnmounted(() => {
cleanup();
});
</script>
<style scoped>
@font-face {
/* 这里给字体起个名字,随便取,只要好记就行,比如 'MyAnimFont' */
font-family: "MyAnimFont";
/* 这里才是放 url 的地方 */
src: url("/static/fonts/animation.ttf") format("truetype");
/* 可选:定义字重和样式,防止浏览器乱猜 */
font-weight: normal;
font-style: normal;
}
.welcome-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background: white;
}
.header-text {
width: 176rpx;
font-size: 64rpx;
background: linear-gradient(45deg, #b52920 20%, #56c1ff, #86d9e0);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
position: absolute;
right: 48rpx;
top: 16rpx;
z-index: 5;
font-family: "MyAnimFont", sans-serif;
}
.header-text::first-letter {
font-size: 96rpx;
margin-left: 8px;
line-height: 1;
}
.background-layer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.bg-media {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.scene-role {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* 场景1独立区域 */
.scene1-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* 场景1对话框样式 */
.scene1-dialog {
position: absolute;
width: 420rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.5s ease;
pointer-events: none;
z-index: 10;
}
.dialog-bg.dialog-bg-oval-1 {
transform: scale(1.95);
}
.dialog-bg.dialog-bg-oval-2 {
transform: scale(5);
}
.dialog-bg.dialog-bg-oval-3 {
transform: scale(4);
}
.dialog-bg.dialog-bg-oval-4 {
transform: scale(1.6);
}
.dialog-bg.dialog-bg-oval-5 {
transform: scale(1.6);
}
.scene1-dialog.visible {
opacity: 1;
transform: translateY(0);
}
/* 场景2三个独立区域 */
.scene2-layout {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* 场景2三个独立区域 - 使用固定高度 */
.scene2-item {
position: absolute;
left: 16rpx;
right: 16rpx;
overflow: hidden;
}
/* 图3: 上部分 */
.scene2-item-1 {
top: 128rpx;
height: 25%;
}
/* 图4: 中部分 */
.scene2-item-2 {
top: calc(128rpx + 25% + 16rpx);
height: 30%;
opacity: 0;
transition: opacity 0.5s ease;
}
.scene2-item-2.visible {
opacity: 1;
}
/* 图5: 下部分 */
.scene2-item-3 {
bottom: 32rpx;
height: 30%;
opacity: 0;
transition: opacity 0.5s ease;
}
.scene2-item-3.visible {
opacity: 1;
}
.scene2-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* 图4视频靠右宽度4/5 */
.scene2-video-right {
left: auto;
right: 0;
width: 80%;
height: 90%;
}
/* 场景2对话框样式 */
.scene2-dialog,
.scene2-dialog-bottom {
position: absolute;
right: 0;
width: 80%;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.5s ease;
pointer-events: auto;
}
.scene2-dialog.visible,
.scene2-dialog-bottom.visible {
opacity: 1;
transform: translateY(0);
}
/* 图4对话框覆盖在图3上面 */
.scene2-dialog-on-video3 {
position: absolute;
width: 420rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.5s ease;
pointer-events: none;
z-index: 10;
}
/* 图5对话框覆盖在图4上面 */
.scene2-dialog-on-video4 {
position: absolute;
width: 420rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.5s ease;
pointer-events: none;
z-index: 10;
}
.scene2-dialog-on-video3.visible,
.scene2-dialog-on-video4.visible {
opacity: 1;
transform: translateY(0);
}
.scene2-dialog {
top: 20%;
}
.scene2-dialog-bottom {
bottom: 20%;
}
.dialog-bg.dialog-bg-oval {
transform: scale(4.5);
}
.dialog-bg.dialog-bg-oval1 {
transform: scale(1.5);
top: 32rpx;
}
/* 场景4两个独立区域 */
.scene4-layout {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.scene4-item {
position: relative;
}
.scene4-item-1 {
margin: 256rpx 16rpx 32rpx 16rpx;
height: 25%;
}
.scene4-item-2 {
height: 30%;
width: 75%;
margin: 0 0 0 auto;
}
.scene4-monster {
position: absolute;
width: 144rpx;
height: 144rpx;
z-index: 1;
transform: scale(4);
}
.scene4-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* 场景4对话框样式 */
.scene4-dialog,
.scene4-dialog-bottom {
position: absolute;
left: 40rpx;
width: 420rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.5s ease;
pointer-events: auto;
}
.scene4-dialog.visible,
.scene4-dialog-bottom.visible {
opacity: 1;
transform: translateY(0);
}
.scene4-dialog {
top: 20%;
}
.scene4-dialog-bottom {
bottom: 20%;
}
/* 场景3上下布局 (上2/3, 下1/3) */
.scene3-layout {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
}
.scene3-top {
height: 60%;
position: relative;
margin: 96rpx 16rpx 32rpx 16rpx;
border-radius: 0.8rpx;
}
.scene3-bottom {
height: 20%;
width: 640rpx;
position: relative;
margin: 0 16rpx 0 auto;
}
.scene3-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* 场景3对话框样式 */
.scene3-top,
.scene3-bottom {
position: relative;
}
.skip-btn {
position: absolute;
top: 80rpx;
right: 40rpx;
z-index: 10;
padding: 16rpx 32rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 30rpx;
border: 2rpx solid rgba(255, 255, 255, 0.5);
}
.skip-text {
font-size: 28rpx;
color: #fff;
}
/* Topfans文字 + 箭头整体 */
.topfans-arrow-wrapper {
position: absolute;
left: 40rpx;
right: 40rpx;
bottom: 0;
transform: translateY(-50%) translateX(-50rpx);
opacity: 0;
transition: all 0.5s ease;
z-index: 5;
display: flex;
align-items: center;
justify-content: space-between;
pointer-events: none;
}
.topfans-arrow-wrapper.visible {
pointer-events: auto;
}
.topfans-arrow-wrapper.visible {
opacity: 1;
transform: translateY(-50%) translateX(0);
}
.topfans-text {
width: 640rpx;
font-size: 96rpx;
background: linear-gradient(45deg, #b52920 10%, #56c1ff, #86d9e0);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-family: "MyAnimFont", sans-serif;
}
.topfans-text::first-letter {
font-size: 112;
/* 设置为首行文字大小的3倍 */
margin-left: 8px;
/* 可选:与后续文字保持间距 */
line-height: 1;
/* 可选:调整行高以防错位 */
}
.arrow-icon {
display: inline-block;
width: 0;
height: 0;
/* 1. 画三角形形状 */
border-top: 15px solid transparent;
border-bottom: 15px solid transparent;
border-left: 25px solid #ffcccc;
/* 这里的颜色会被渐变覆盖,随便写个浅色即可 */
/* 2. 设置渐变色 (从左到右:粉色 -> 黄色) */
/* 注意:为了让渐变填满三角形,我们需要一个伪元素或者特殊技巧,
但更简单的方法是直接用 background-image 配合 clip-path (现代浏览器)
或者保持上面的 border 写法,用 filter 上色(兼容性最好但渐变难做)。
【修正方案】:为了完美渐变 + 阴影,我们用 clip-path 画法更精准:
*/
width: 128rpx;
/* 箭头总宽 */
height: 64rpx;
/* 箭头总高 */
border: none;
/* 清除边框画法 */
/* 使用背景渐变 */
background: linear-gradient(35deg, rgba(255, 107, 157, 0.9) 35%, rgba(255, 177, 153, 0.9) 70%);
/* 使用 clip-path 切割出“尾巴+三角”的形状 */
/* 这是一个类似图片中形状的坐标点:左边有个小尾巴,右边是大三角 */
clip-path: polygon(0% 40%,
/* 左上尾巴起点 */
40% 40%,
/* 尾巴连接处上 */
40% 0%,
/* 三角形顶点上 */
100% 50%,
/* 最右侧尖端 */
40% 100%,
/* 三角形顶点下 */
40% 60%,
/* 尾巴连接处下 */
0% 60%
/* 左下尾巴终点 */
);
/* 3. 添加立体阴影 (关键步骤) */
/* drop-shadow 比 box-shadow 更适合不规则形状 */
filter: drop-shadow(2px 3px 2px rgba(0, 0, 0, 0.3));
vertical-align: middle;
margin: 0 10px;
}
/* 内容层 */
.content-layer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.dialog-wrapper {
position: absolute;
left: 35%;
top: 20%;
width: 420rpx;
opacity: 0;
transform: translateY(20rpx);
transition: all 0.5s ease;
pointer-events: auto;
}
.dialog-wrapper.visible {
opacity: 1;
transform: translateY(0);
}
.dialog-bg {
position: absolute;
top: 8rpx;
left: 0;
width: 100%;
height: 200rpx;
z-index: 0;
transform: scale(1.2);
pointer-events: none;
}
.dialog-text-wrapper {
position: relative;
z-index: 1;
padding: 40rpx 50rpx 50rpx 50rpx;
min-height: 120rpx;
display: flex;
align-items: center;
left: 15%;
}
.dialog-text {
font-size: 30rpx;
color: #333;
line-height: 1.5;
font-weight: 500;
}
/* 场景1文字上下居中样式 */
.dialog-text-wrapper-vertical {
position: relative;
left: 80rpx;
z-index: 1;
width: 256rpx;
height: 200rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.dialog-text-wrapper-vertical.dialog-text-wrapper-vertical-on-video3 {
left: 48rpx;
width: 320rpx;
}
.dialog-text-wrapper-vertical.dialog-text-wrapper-vertical-on-video4 {
left: 56rpx;
bottom: 32rpx;
width: 320rpx;
}
.dialog-text-wrapper-vertical.dialog-text-wrapper-vertical-on-video5 {
left: 24rpx;
width: 336rpx;
}
.dialog-text-wrapper-vertical.dialog-text-wrapper-vertical-on-video6 {
width: 224rpx;
left: 80rpx;
}
.dialog-text-wrapper-vertical.dialog-text-wrapper-vertical-on-video7 {
width: 352rpx;
bottom: 16rpx;
left: 80rpx;
}
.dialog-text-wrapper-vertical.dialog-text-wrapper-vertical-on-video8 {
width: 356rpx;
left: 80rpx;
}
.dialog-text-vertical {
font-size: 24rpx;
color: #333;
font-weight: 500;
text-align: center;
letter-spacing: 4rpx;
line-height: 40rpx;
}
.dialog-text-vertical.dialog-text-vertical-on-video5 {
font-size: 32rpx;
}
.monster {
position: absolute;
right: 160rpx;
bottom: 20%;
width: 280rpx;
height: 280rpx;
opacity: 0;
transform: translateX(50rpx);
transition: all 0.6s ease;
pointer-events: auto;
}
.monster.visible {
opacity: 1;
transform: translateX(0);
}
/* 场景6背景 */
.scene6-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.scene6-bg {
position: relative;
height: 100%;
}
</style>