poster scroll
This commit is contained in:
parent
eb6b6c41f3
commit
38005d4a42
@ -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: '奥莱',
|
||||
|
||||
78
src/assets/ani/crop_script.py
Normal file
78
src/assets/ani/crop_script.py
Normal 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.
@ -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');
|
||||
}
|
||||
|
||||
// 8秒后如果用户没有看过p2,自动滚动到p2
|
||||
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) {
|
||||
// 滚动到p2位置(第二个元素,占总宽度的50%)
|
||||
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; ">
|
||||
<AniEle :url="assets.ani.海报p1" ref="gui-ani-end-post-p1"
|
||||
style="width: 100%;" class="gui-ani-end-post"
|
||||
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: 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>
|
||||
|
||||
@ -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'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user