poster scroll

This commit is contained in:
feie9456 2025-07-22 09:17:49 +08:00
parent eb6b6c41f3
commit 38005d4a42
7 changed files with 159 additions and 15 deletions

View File

@ -5,7 +5,7 @@ import Page1 from './pages/Page1.vue';
import assets from './assets';
import Loader from './pages/Loader.vue';
const stage = ref(-1);
const stage = ref(1);
const userData = ref({
region: '奥莱',

View File

@ -0,0 +1,78 @@
import os
from PIL import Image
import re
# --- 配置 ---
# 目标尺寸
TARGET_WIDTH = 670
TARGET_HEIGHT = 939
# 裁剪区域 (left, upper, right, lower)
# 我们从左上角 (0, 0) 开始,裁剪出一个 670x939 的区域
CROP_BOX = (0, 0, TARGET_WIDTH, TARGET_HEIGHT)
# 输出文件夹名称
OUTPUT_DIR = "cropped"
# --- 结束配置 ---
def batch_crop_images():
"""
批量裁剪当前目录下的 WebP 图片
"""
# 获取当前脚本所在的目录
current_dir = os.getcwd()
output_path = os.path.join(current_dir, OUTPUT_DIR)
# 如果输出目录不存在,则创建它
if not os.path.exists(output_path):
os.makedirs(output_path)
print(f"已创建输出目录: {output_path}")
# 获取目录下所有文件
files = os.listdir(current_dir)
# 定义一个正则表达式来匹配 '数字.webp' 格式的文件名
file_pattern = re.compile(r"^\d+\.webp$")
image_files_to_process = [f for f in files if file_pattern.match(f)]
if not image_files_to_process:
print("未在当前目录找到符合 '数字.webp' 格式的图片文件。")
return
print(f"找到了 {len(image_files_to_process)} 个待处理的图片文件。")
# 遍历所有符合条件的图片文件
for filename in image_files_to_process:
try:
# 构建完整的文件路径
file_path = os.path.join(current_dir, filename)
# 打开图片
with Image.open(file_path) as img:
print(f"正在处理: {filename} (原始尺寸: {img.size[0]}x{img.size[1]})")
# 检查原始尺寸是否符合预期
if img.size != (671, 940):
print(f" -> 警告: {filename} 的尺寸 ({img.size[0]}x{img.size[1]}) 与预期的 671x940 不符,已跳过。")
continue
# 进行裁剪
cropped_img = img.crop(CROP_BOX)
# 构建输出文件路径
save_path = os.path.join(output_path, filename)
# 保存裁剪后的图片
# quality=100 可以尽量保持高质量,你可以根据需要调整
cropped_img.save(save_path, "WEBP", quality=100)
print(f" -> 已裁剪并保存至: {save_path} (新尺寸: {cropped_img.size[0]}x{cropped_img.size[1]})")
except Exception as e:
print(f"处理 {filename} 时发生错误: {e}")
print("\n所有图片处理完成!")
if __name__ == "__main__":
batch_crop_images()

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -22,10 +22,14 @@ const props = defineProps<{
const sunEle = useTemplateRef('sun-ani');
const sunEndEle = useTemplateRef('sun-ani-end');
const posterP2Ele = useTemplateRef('gui-ani-end-post-p2');
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const emit = defineEmits(['cloudUp', 'cloudDown', 'restart']);
// Intersection Observer for p2 animation
const hasP2AnimationPlayed = ref(false);
async function shoot() {
//sunEle.value?.jumpTo('')
sunEle.value?.jumpTo('蓄力飞')
@ -52,7 +56,7 @@ async function shoot() {
emit('cloudUp');
document.querySelector('.bed')?.animate([
{ transform: 'translateY(0)' },
{ transform: 'translateY(150%)' },
{ transform: 'translateY(170%)' },
], {
duration: 500,
delay: 200,
@ -547,7 +551,7 @@ async function gameEnd() {
})
document.querySelector('.bed')?.animate([
{ transform: 'translateY(150%)' },
{ transform: 'translateY(170%)' },
{ transform: 'translateY(0)' },
], {
duration: 600,
@ -592,7 +596,7 @@ async function gameEnd() {
document.querySelector('.bed')?.animate([
{ transform: 'translateY(0)' },
{ transform: 'translateY(150%)' },
{ transform: 'translateY(170%)' },
], {
duration: 600,
easing: 'cubic-bezier(0.4, 0, 1, 0.5)',
@ -713,9 +717,48 @@ async function finishCollect() {
showLastPage.value = true;
// Intersection Observer p2
await wait(100); // DOM
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !hasP2AnimationPlayed.value) {
// p2
hasP2AnimationPlayed.value = true;
posterP2Ele.value?.jumpTo('p2');
console.log('Poster P2 animation triggered');
//
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.5, // 50%
rootMargin: '0px'
});
// p2
const p2Element = posterP2Ele.value?.$el;
if (p2Element) {
observer.observe(p2Element);
console.log('Started observing P2 element');
} else {
console.warn('P2 element not found');
}
// 8p2p2
setTimeout(() => {
if (!posterHasChangedToPage2.value) posterHasChangedToPage2.value = true;
}, 8000);
if (!hasP2AnimationPlayed.value) {
console.log('Auto scrolling to P2 after 8 seconds');
const posterContainer = document.querySelector('.poster-container');
if (posterContainer) {
// p250%
posterContainer.scrollTo({
left: posterContainer.scrollWidth * 0.5,
behavior: 'smooth'
});
}
}
}, 8000); // 8 = 8000
}
@ -883,17 +926,17 @@ function posterSwap(e: PointerEvent) {
:rules="[{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }]"
style="position: absolute;top: 13%;width: 80%;left: 11%; pointer-events:none;"
ref="main-logo" />
<div class="poster-container" @pointermove="posterSwap"
style="position: absolute;top: 29.3%;left: 37.2%;width: 57.3%; height: 38%; overflow: hidden;pointer-events: all;">
<div class="poster-container"
style="position: absolute;top: 29.3%;left: 37.2%;width: 57.3%; height: 38%; overflow-x: auto; overflow-y: hidden; pointer-events: all; scroll-snap-type: x mandatory;">
<div class="poster-wrapper"
style="width: 200%; height: 100%; position: relative; overflow-x: scroll; display: flex; ">
style="display: flex; width: 200%; height: 100%; flex-direction: row;" dir="ltr" >
<AniEle :url="assets.ani.海报p1" ref="gui-ani-end-post-p1 "
style="width: 100%;" class="gui-ani-end-post"
style="width: 50%; flex-shrink: 0; scroll-snap-align: start;" class="gui-ani-end-post"
:height="940" :width="671" :rules="[
{ name: 'p1', frame: 32, loop: 1, pauseAfter: true, duration: 33 },
]" />
<AniEle :url="assets.ani.海报p2" ref="gui-ani-end-post-p2"
style="width: 100%;" class="gui-ani-end-post"
style="width: 50%; flex-shrink: 0; scroll-snap-align: start;" class="gui-ani-end-post"
:height="940" :width="671" :rules="[
{ name: 'p2', frame: 32, loop: 1, pauseAfter: true, duration: 33 },
]" />
@ -959,6 +1002,11 @@ function posterSwap(e: PointerEvent) {
</template>
<style lang="scss">
.gui-ani-end-post {
/* 确保每个海报都能正确对齐 */
object-fit: contain;
}
@keyframes last-btn-in {
0% {
bottom: -19%;
@ -1297,4 +1345,24 @@ function posterSwap(e: PointerEvent) {
animation: scale-in 0.4s ease-in-out forwards;
}
}
.poster-container {
/* 隐藏滚动条但保持滚动功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
}
.poster-wrapper {
/* 确保滚动流畅 */
scroll-behavior: smooth;
}
.gui-ani-end-post {
/* 确保每个海报都能正确对齐 */
object-fit: contain;
}
</style>

View File

@ -1,12 +1,10 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import minipic from 'vite-plugin-minipic'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
minipic()
vue()
],
server: {
host: '0.0.0.0'