This commit is contained in:
2025-07-08 11:34:29 +08:00
parent cd7a18b63f
commit 314b03a9ab
2 changed files with 718 additions and 153 deletions
+249 -153
View File
@@ -2,7 +2,9 @@
import { onMounted } from 'vue';
import assets from '../assets';
const regions = [
import { regions } from '../data';
const regionsPos = [
{ w: 68.8, h: 68.8, l: -1, t: -4, src: assets.p1.大北区, tt: '大北区', trt: -13, ad: 6 },
{ w: 73.3, h: 73.3, l: 44, t: 20, src: assets.p1.大东区, tt: '大东区', ad: 3 },
{ w: 62.3, h: 63.7, l: -32, t: 72, src: assets.p1['大南区&免税'], tt: '大南区&免税', trt: 5, ad: 3 },
@@ -10,41 +12,194 @@ const regions = [
{ w: 52.1, h: 52, l: 3, t: 40, src: assets.p1.奥莱, tt: '奥莱', ad: 2 },
{ w: 62.7, h: 62.7, l: 33, t: 74, src: assets.p1.博物馆, tt: '博物馆', tc: '#fff', ad: 0 },
]
onMounted(() => {
const regionBubbles = document.querySelectorAll('.region-bubble');
regionBubbles.forEach((bubble, index) => {
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'
});
}
const pUp = () => {
bubble.querySelector('span')?.animate([
{ opacity: 1 },
{ opacity: 0 },
], {
duration: 1000,
easing: 'ease-in-out',
fill: 'forwards'
});
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'
});
// 其他元素淡出
regionBubbles.forEach((otherBubble, otherIndex) => {
if (otherIndex == index) return
otherBubble.animate([
// 动画管理
let fadeInAnimations: Animation[] = [];
let swapInAnimations: Animation[] = [];
let scaleInAnimations: 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;
}
// Scale in 动画(容器)
function createScaleInContainerAnimation(element: Element, delay: number = 0) {
const animation = element.animate([
{ transform: 'scale(0)' },
{ transform: 'scale(1)' }
], {
duration: 500,
easing: 'cubic-bezier(0.5, 0, 0.71, 1.85)',
delay: (delay + 0.2) * 1000,
fill: 'forwards'
});
scaleInAnimations.push(animation);
allAnimations.push(animation);
return animation;
}
// Scale in 动画(文本)
function createScaleInTextAnimation(element: Element, delay: number = 0) {
const animation = element.animate([
{ transform: 'scale(0)' },
{ transform: 'scale(1)' }
], {
duration: 500,
easing: 'cubic-bezier(0.5, 0, 0.71, 1.5)',
delay: (delay + 0.2 + 0.1) * 1000,
fill: 'forwards'
});
scaleInAnimations.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;
}
// 反向播放所有动画来淡出
function reverseAllAnimations() {
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;
onMounted(() => {
// 等待一帧确保所有元素都已渲染
requestAnimationFrame(() => {
// 初始化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);
});
// 初始化scale-in动画
const scaleInContainers = document.querySelectorAll('.scale-in-ctn');
scaleInContainers.forEach((element) => {
const htmlElement = element as HTMLElement;
const delay = parseFloat(htmlElement.style.getPropertyValue('--base-delay')?.replace('s', '') || '0');
createScaleInContainerAnimation(element, delay);
});
const scaleInTexts = document.querySelectorAll('.scale-in-txt');
scaleInTexts.forEach((element) => {
const htmlElement = element as HTMLElement;
const delay = parseFloat(htmlElement.style.getPropertyValue('--base-delay')?.replace('s', '') || '0');
createScaleInTextAnimation(element, delay);
});
// 初始化气泡动画
const regionBubbles = document.querySelectorAll('.region-bubble');
regionBubbles.forEach((bubble, index) => {
const region = regionsPos[index];
const startTop = `${region.t + 50 - 200 - (100 - region.t)}%`;
const startLeft = `${region.l / 2 + 50}%`;
const endTop = `${region.t + 50}%`;
const endLeft = `${region.l + 50}%`;
// 设置初始位置
(bubble as HTMLElement).style.top = startTop;
(bubble as HTMLElement).style.left = startLeft;
createBubbleDropAnimation(bubble, startTop, startLeft, endTop, endLeft, 0, 600 + region.ad * 100);
// 点击事件
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'
});
}
const pUp = () => {
bubble.querySelector('span')?.animate([
{ opacity: 1 },
{ opacity: 0 },
], {
@@ -52,13 +207,38 @@ onMounted(() => {
easing: 'ease-in-out',
fill: 'forwards'
});
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'
});
});
// 反向播放所有动画来淡出其他元素
reverseAllAnimations();
bubble.removeEventListener('pointerdown', pDown);
}
bubble.addEventListener('pointerdown', pDown);
bubble.addEventListener('pointerup', pUp, { once: true });
// 淡出其他气泡
regionBubbles.forEach((otherBubble, otherIndex) => {
if (otherIndex == index) return
otherBubble.animate([
{ opacity: 1 },
{ opacity: 0 },
], {
duration: 1000,
easing: 'ease-in-out',
fill: 'forwards'
});
});
bubble.removeEventListener('pointerdown', pDown);
}
bubble.addEventListener('pointerdown', pDown);
bubble.addEventListener('pointerup', pUp, { once: true });
});
});
});
@@ -66,18 +246,18 @@ onMounted(() => {
<template>
<section class="p1">
<img :src="assets.标准logo" alt="" style="top: 6%;left: 8%;width: 12%;--base-delay: 2s;" class="fade-in">
<img :src="assets.标准logo" alt="" class="logo" style="top: 6%;left: 8%;width: 12%;opacity: 0;">
<img :src="assets.p1.蓝天" alt="" class="bg">
<div style="height: 12vw;top: 11.4%;left: 6%">
<span class="green scale-in-ctn" style="border-radius: 6vw;font-size: 9vw; padding: 0 3vw;height: 100%;">
<span class="scale-in-txt">太阳脸</span>
<span class="green scale-in-ctn" style="border-radius: 6vw;font-size: 9vw; padding: 0 3vw;height: 100%;transform: scale(0);--base-delay: 0s;">
<span class="scale-in-txt" style="transform: scale(0);">太阳脸</span>
</span>
<span class="green fake scale-in-txt" style="border-radius: 6vw;font-size: 9vw; padding: 0 3vw;height: 100%;">
<span class="scale-in-txt">太阳脸</span>
<span class="green fake scale-in-txt" style="border-radius: 6vw;font-size: 9vw; padding: 0 3vw;height: 100%;transform: scale(0);--base-delay: 0s;">
<span class="scale-in-txt" style="transform: scale(0);">太阳脸</span>
</span>
<span class="green scale-in-ctn"
style="width: 12vw;border-radius: 5vw; padding: 2vw 2vw 2vw 3vw; left: 34vw;height: 100%;">
<span class="green scale-in-ctn"
style="width: 12vw;border-radius: 5vw; padding: 2vw 2vw 2vw 3vw; left: 34vw;height: 100%;transform: scale(0);--base-delay: 0s;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 91.18 90.18" preserveAspectRatio="xMidYMid meet">
<path fill-rule="evenodd" stroke="rgb(255, 255, 255)" stroke-width="12.12px" stroke-linecap="round"
@@ -87,7 +267,7 @@ onMounted(() => {
</svg>
</span>
<span class="green fake scale-in-txt"
style="width: 12vw;border-radius: 5vw; padding: 2vw 2vw 2vw 3vw; left: 34vw;height: 100%;">
style="width: 12vw;border-radius: 5vw; padding: 2vw 2vw 2vw 3vw; left: 34vw;height: 100%;transform: scale(0);--base-delay: 0s;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 91.18 90.18" preserveAspectRatio="xMidYMid meet">
<path fill-rule="evenodd" stroke="rgb(255, 255, 255)" stroke-width="12.12px" stroke-linecap="round"
@@ -99,39 +279,36 @@ onMounted(() => {
</div>
<div style="left: 12%;top: 17.2%;width: 73vw;height: 7%;">
<span class="black scale-in-ctn" style="border-radius: 7vw;height: 100%;width: 100%;--base-delay:0.1s">
<span class="black scale-in-ctn" style="border-radius: 7vw;height: 100%;width: 100%;transform: scale(0);--base-delay:0.1s">
<span>弹性福利之旅</span>
</span>
<span class="black fake scale-in-txt" style="border-radius: 7vw;height: 100%;width: 100%;--base-delay:0.1s">
<span class="black fake scale-in-txt" style="border-radius: 7vw;height: 100%;width: 100%;transform: scale(0);--base-delay:0.1s">
<span>弹性福利之旅</span>
</span>
<span class="green scale-in-ctn"
style="padding: 0 1.5vw;top: -17%;font-size: 3.5vw;right: -6%;height: 30%;border-radius: 3vw;--base-delay:0.2s">
style="padding: 0 1.5vw;top: -17%;font-size: 3.5vw;right: -6%;height: 30%;border-radius: 3vw;transform: scale(0);--base-delay:0.2s">
<span class="elan block relative">TOUR</span>
</span>
<span class="green fake scale-in-txt"
style="padding: 0 1.5vw;top: -17%;font-size: 3.5vw;right: -6%;height: 30%;border-radius: 3vw;--base-delay:0.2s">
style="padding: 0 1.5vw;top: -17%;font-size: 3.5vw;right: -6%;height: 30%;border-radius: 3vw;transform: scale(0);--base-delay:0.2s">
<span class="elan block relative">TOUR</span>
</span>
</div>
<div style="top: 11.7%;left: 53%; display: flex; flex-direction: column;">
<span class="white relative swap-in" style="left: 30%;height: 5.5vw;--base-delay:0.6s">
<span class="white relative swap-in" style="left: 30%;height: 5.5vw;clip-path: inset(0 100% 0 0);--base-delay:0.6s">
<span class="elan">FLEXIBEL</span>
</span>
<span class="white relative swap-in" style="height: 5.5vw;--base-delay:0.9s">
<span class="white relative swap-in" style="height: 5.5vw;clip-path: inset(0 100% 0 0);--base-delay:0.9s">
<span class="elan">BENEFITS</span>
</span>
</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 regions" :style="{
width: i.w + '%', height: i.h + '%', left: (i.l + 50) + '%', top: (i.t + 50) + '%',
'--t-start': (i.t + 50 - 200 - (100 - i.t)) + '%',
'--l-start': (i.l / 2 + 50) + '%',
'--t-end': (i.t + 50) + '%',
'--l-end': (i.l + 50) + '%',
animationDuration: (600 + i.ad * 100) + 'ms',
<div v-for="i in regionsPos" :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%;">
@@ -144,22 +321,22 @@ onMounted(() => {
</div>
<div style="color: white; bottom: 16%;left: 50%;transform: translateX(-50%); font-size: 4.5vw;">
<span class="block tag scale-in-ctn" style="background-color: #000;top: -1vw;left: -12vw;color: transparent; --base-delay:1s;">
<span class="block tag scale-in-ctn" style="background-color: #000;top: -1vw;left: -12vw;color: transparent;transform: scale(0);--base-delay:1s;">
<span>点击</span>
</span>
<span class="block tag scale-in-ctn" style="background-color: #304A7B;top: 4vw;left: -6vw;color: transparent;--base-delay:1.1s;">
<span class="block tag scale-in-ctn" style="background-color: #304A7B;top: 4vw;left: -6vw;color: transparent;transform: scale(0);--base-delay:1.1s;">
<span>选择</span>
</span>
<span class="block tag scale-in-ctn" style="background-color: #8FBE56;top: .8vw;left: 4.5vw;color: transparent;--base-delay:1.1s;">
<span class="block tag scale-in-ctn" style="background-color: #8FBE56;top: .8vw;left: 4.5vw;color: transparent;transform: scale(0);--base-delay:1.1s;">
<span>阵营</span>
</span>
<span class="block tag scale-in-txt" style="background-color: transparent;top: -1vw;left: -12vw;--base-delay:1.2s;">
<span class="block tag scale-in-txt" style="background-color: transparent;top: -1vw;left: -12vw;transform: scale(0);--base-delay:1.2s;">
<span>点击</span>
</span>
<span class="block tag scale-in-txt" style="background-color: transparent;top: 4vw;left: -6vw;--base-delay:1.2s;">
<span class="block tag scale-in-txt" style="background-color: transparent;top: 4vw;left: -6vw;transform: scale(0);--base-delay:1.2s;">
<span>选择</span>
</span>
<span class="block tag scale-in-txt" style="background-color: transparent;top: .8vw;left: 4.5vw;--base-delay:1.3s;">
<span class="block tag scale-in-txt" style="background-color: transparent;top: .8vw;left: 4.5vw;transform: scale(0);--base-delay:1.3s;">
<span>阵营</span>
</span>
</div>
@@ -173,92 +350,6 @@ onMounted(() => {
align-items: center;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
opacity: 0;
animation: fade-in 0.5s ease-in-out forwards;
animation-delay: var(--base-delay, 0);
}
/* 使用 @property 注册你的自定义属性 */
@property --gradient-stop {
syntax: '<percentage>'; /* 告诉浏览器这是一个百分比 */
inherits: false; /* 这个属性不被子元素继承 */
initial-value: -20%; /* 定义一个初始值 */
}
@keyframes swap-in {
/* from/to 也可以,这里 0%/100% 没问题 */
from {
--gradient-stop: -20%;
}
to {
--gradient-stop: 100%;
}
}
.swap-in {
mask-image: linear-gradient(to right, black var(--gradient-stop), transparent calc(var(--gradient-stop) + 20%));
mask-repeat: no-repeat;
mask-size: 100% 100%;
animation: swap-in 0.5s ease-in-out forwards;
animation-delay: var(--base-delay, 0);
}
@keyframes bubble-drop {
0% {
top: var(--t-start);
left: var(--l-start);
transform: translate3d(-50%, -50%, 0) scale(0.1);
}
100% {
top: var(--t-end);
left: var(--l-end);
transform: translate3d(-50%, -50%, 0) scale(1);
}
}
.region-bubble {
transform: translate3d(-50%, -50%, 0);
animation: bubble-drop 2s ease-out forwards;
animation-delay: 1s;
top: var(--t-start);
left: var(--l-start);
transform: translate3d(-50%, -50%, 0) scale(0);
}
@keyframes scale-in {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
.scale-in-ctn {
transform: scale(0);
animation: scale-in 0.5s cubic-bezier(0.5, 0, 0.71, 1.85) forwards;
animation-delay: calc(var(--base-delay, 0) + 0.2s);
}
.scale-in-txt {
transform: scale(0);
animation: scale-in 0.5s cubic-bezier(0.5, 0, 0.71, 1.5) forwards;
animation-delay: calc(var(--base-delay, 0) + 0.2s + 0.1s);
}
.tag {
width: max-content;
padding: 0.5vw .8vw;
@@ -308,6 +399,11 @@ onMounted(() => {
}
}
.region-bubble {
position: absolute;
transform: translate3d(-50%, -50%, 0);
}
span,
div {
position: absolute;