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 assets from './assets';
|
||||||
import Loader from './pages/Loader.vue';
|
import Loader from './pages/Loader.vue';
|
||||||
|
|
||||||
const stage = ref(-1);
|
const stage = ref(1);
|
||||||
|
|
||||||
const userData = ref({
|
const userData = ref({
|
||||||
region: '奥莱',
|
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 sunEle = useTemplateRef('sun-ani');
|
||||||
const sunEndEle = useTemplateRef('sun-ani-end');
|
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 wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
const emit = defineEmits(['cloudUp', 'cloudDown', 'restart']);
|
const emit = defineEmits(['cloudUp', 'cloudDown', 'restart']);
|
||||||
|
|
||||||
|
// Intersection Observer for p2 animation
|
||||||
|
const hasP2AnimationPlayed = ref(false);
|
||||||
|
|
||||||
async function shoot() {
|
async function shoot() {
|
||||||
//sunEle.value?.jumpTo('天上飞')
|
//sunEle.value?.jumpTo('天上飞')
|
||||||
sunEle.value?.jumpTo('蓄力飞')
|
sunEle.value?.jumpTo('蓄力飞')
|
||||||
@ -52,7 +56,7 @@ async function shoot() {
|
|||||||
emit('cloudUp');
|
emit('cloudUp');
|
||||||
document.querySelector('.bed')?.animate([
|
document.querySelector('.bed')?.animate([
|
||||||
{ transform: 'translateY(0)' },
|
{ transform: 'translateY(0)' },
|
||||||
{ transform: 'translateY(150%)' },
|
{ transform: 'translateY(170%)' },
|
||||||
], {
|
], {
|
||||||
duration: 500,
|
duration: 500,
|
||||||
delay: 200,
|
delay: 200,
|
||||||
@ -547,7 +551,7 @@ async function gameEnd() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
document.querySelector('.bed')?.animate([
|
document.querySelector('.bed')?.animate([
|
||||||
{ transform: 'translateY(150%)' },
|
{ transform: 'translateY(170%)' },
|
||||||
{ transform: 'translateY(0)' },
|
{ transform: 'translateY(0)' },
|
||||||
], {
|
], {
|
||||||
duration: 600,
|
duration: 600,
|
||||||
@ -592,7 +596,7 @@ async function gameEnd() {
|
|||||||
|
|
||||||
document.querySelector('.bed')?.animate([
|
document.querySelector('.bed')?.animate([
|
||||||
{ transform: 'translateY(0)' },
|
{ transform: 'translateY(0)' },
|
||||||
{ transform: 'translateY(150%)' },
|
{ transform: 'translateY(170%)' },
|
||||||
], {
|
], {
|
||||||
duration: 600,
|
duration: 600,
|
||||||
easing: 'cubic-bezier(0.4, 0, 1, 0.5)',
|
easing: 'cubic-bezier(0.4, 0, 1, 0.5)',
|
||||||
@ -713,9 +717,48 @@ async function finishCollect() {
|
|||||||
|
|
||||||
showLastPage.value = true;
|
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(() => {
|
setTimeout(() => {
|
||||||
if (!posterHasChangedToPage2.value) posterHasChangedToPage2.value = true;
|
if (!hasP2AnimationPlayed.value) {
|
||||||
}, 8000);
|
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 }]"
|
: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;"
|
style="position: absolute;top: 13%;width: 80%;left: 11%; pointer-events:none;"
|
||||||
ref="main-logo" />
|
ref="main-logo" />
|
||||||
<div class="poster-container" @pointermove="posterSwap"
|
<div class="poster-container"
|
||||||
style="position: absolute;top: 29.3%;left: 37.2%;width: 57.3%; height: 38%; overflow: hidden;pointer-events: all;">
|
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"
|
<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"
|
<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="[
|
:height="940" :width="671" :rules="[
|
||||||
{ name: 'p1', frame: 32, loop: 1, pauseAfter: true, duration: 33 },
|
{ name: 'p1', frame: 32, loop: 1, pauseAfter: true, duration: 33 },
|
||||||
]" />
|
]" />
|
||||||
<AniEle :url="assets.ani.海报p2" ref="gui-ani-end-post-p2"
|
<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="[
|
:height="940" :width="671" :rules="[
|
||||||
{ name: 'p2', frame: 32, loop: 1, pauseAfter: true, duration: 33 },
|
{ name: 'p2', frame: 32, loop: 1, pauseAfter: true, duration: 33 },
|
||||||
]" />
|
]" />
|
||||||
@ -959,6 +1002,11 @@ function posterSwap(e: PointerEvent) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.gui-ani-end-post {
|
||||||
|
/* 确保每个海报都能正确对齐 */
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes last-btn-in {
|
@keyframes last-btn-in {
|
||||||
0% {
|
0% {
|
||||||
bottom: -19%;
|
bottom: -19%;
|
||||||
@ -1297,4 +1345,24 @@ function posterSwap(e: PointerEvent) {
|
|||||||
animation: scale-in 0.4s ease-in-out forwards;
|
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>
|
</style>
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import minipic from 'vite-plugin-minipic'
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue()
|
||||||
minipic()
|
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0'
|
host: '0.0.0.0'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user