进出动画效果

This commit is contained in:
2026-02-27 16:03:45 +08:00
parent 8478c5bd7f
commit d17e8a48eb
5 changed files with 82 additions and 39 deletions

View File

@@ -14,7 +14,7 @@
</template>
<script setup>
import { ref, computed, useSlots, watch, nextTick } from 'vue'
import { ref, computed, useSlots, nextTick } from 'vue'
const slots = useSlots()
const pageCount = computed(() => {
@@ -35,21 +35,34 @@ const wrapperStyle = computed(() => ({
function goTo(index) {
if (isAnimating.value || index === currentIndex.value) return
if (index < 0 || index >= pageCount.value) return
direction.value = index > currentIndex.value ? 'down' : 'up'
const prevIndex = currentIndex.value
direction.value = index > prevIndex ? 'down' : 'up'
isAnimating.value = true
triggerLeaveAnimation(prevIndex)
currentIndex.value = index
triggerPageAnimation(index)
triggerEnterAnimation(index)
setTimeout(() => { isAnimating.value = false }, 900)
}
function triggerPageAnimation(index) {
function triggerLeaveAnimation(index) {
nextTick(() => {
const pages = document.querySelectorAll('.fullpage-wrapper > *')
const page = pages[index]
if (!page) return
page.classList.remove('page-enter')
page.classList.remove('page-leave-up', 'page-leave-down', 'page-enter-up', 'page-enter-down')
void page.offsetWidth
page.classList.add('page-enter')
page.classList.add(direction.value === 'down' ? 'page-leave-up' : 'page-leave-down')
})
}
function triggerEnterAnimation(index) {
nextTick(() => {
const pages = document.querySelectorAll('.fullpage-wrapper > *')
const page = pages[index]
if (!page) return
page.classList.remove('page-leave-up', 'page-leave-down', 'page-enter-up', 'page-enter-down')
void page.offsetWidth
page.classList.add(direction.value === 'down' ? 'page-enter-down' : 'page-enter-up')
})
}
@@ -142,26 +155,56 @@ defineExpose({ currentIndex, goTo })
box-shadow: 0 0 8px rgba(0, 191, 165, 0.6);
}
/* 页面入场动画 - 仅针对非绝对定位元素 */
:global(.page-enter .text-side),
:global(.page-enter .image-side),
:global(.page-enter .content-wrapper) {
animation: fadeSlideUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
/* 页面内元素进场/离场动画,通过 .anim-item 标记需要动画的元素 */
:global(.page-enter-down .anim-item) {
animation: enterFromBottom 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: 0.5s;
}
:global(.page-enter .image-side) {
animation-delay: 0.1s;
:global(.page-enter-up .anim-item) {
animation: enterFromTop 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: 0.5s;
}
@keyframes fadeSlideUp {
from {
opacity: 0;
transform: translateY(32px);
}
to {
opacity: 1;
transform: translateY(0);
}
:global(.page-leave-up .anim-item) {
animation: leaveToTop 0.32s cubic-bezier(0.76, 0, 0.24, 1) both;
}
:global(.page-leave-down .anim-item) {
animation: leaveToBottom 0.32s cubic-bezier(0.76, 0, 0.24, 1) both;
}
/* 子元素依次错开出现(仅进场) */
:global(.page-enter-down .anim-item:nth-child(1)),
:global(.page-enter-up .anim-item:nth-child(1)) { animation-delay: 0.5s; }
:global(.page-enter-down .anim-item:nth-child(2)),
:global(.page-enter-up .anim-item:nth-child(2)) { animation-delay: 0.62s; }
:global(.page-enter-down .anim-item:nth-child(3)),
:global(.page-enter-up .anim-item:nth-child(3)) { animation-delay: 0.74s; }
:global(.page-enter-down .anim-item:nth-child(4)),
:global(.page-enter-up .anim-item:nth-child(4)) { animation-delay: 0.86s; }
:global(.page-enter-down .anim-item:nth-child(5)),
:global(.page-enter-up .anim-item:nth-child(5)) { animation-delay: 0.98s; }
@keyframes enterFromBottom {
from { opacity: 0; transform: translateY(36px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes enterFromTop {
from { opacity: 0; transform: translateY(-36px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes leaveToTop {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-24px); }
}
@keyframes leaveToBottom {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(24px); }
}
@media (max-width: 768px) {

View File

@@ -2,16 +2,16 @@
<section class="page page-advantage">
<div class="content-wrapper">
<div class="image-side">
<img src="/src/assets/images/three-phone.png" class="phone-img" />
<img src="/src/assets/images/three-phone.png" class="phone-img anim-item" />
</div>
<div class="text-side">
<h2 class="section-title">
<h2 class="section-title anim-item">
Every opening is an<br />
unknown encounter.
</h2>
<div class="section-card">
<div class="section-card anim-item">
<img src="/src/assets/images/three-douhao.png" class="douhao-img">
<p class="section-desc">
<p class="section-desc anim-item">
Every moment you open it, you embark on a poetic encounter with the unknown, and meet the unexpected beauty.
</p>
<div class="hero-buttons">

View File

@@ -2,16 +2,16 @@
<section class="page page-contact">
<div class="content-wrapper">
<div class="text-side">
<h2 class="section-title">
<h2 class="section-title anim-item">
Customize Keyboard<br />
AI Reply .
</h2>
<p class="section-desc">
<p class="section-desc anim-item">
Personalize your chat keyboard and get tailored, high-EQ responses for every conversation
</p>
</div>
<div class="image-side">
<img src="/src/assets/images/four-phone.png" class="phone-img" />
<img src="/src/assets/images/four-phone.png" class="phone-img anim-item" />
</div>
</div>

View File

@@ -6,16 +6,16 @@
<div class="hero">
<div class="hero-phones">
<div class="hero-content">
<h1 class="hero-title">
<h1 class="hero-title anim-item">
Not just a tool but<br />
giving meaning to every click
</h1>
<div class="hero-buttons">
<div class="hero-buttons anim-item">
<a href="#"><img src="/src/assets/images/android.png" class="btn-img" /></a>
<a href="#"><img src="/src/assets/images/appStore.png" class="btn-img" /></a>
</div>
</div>
<img src="/src/assets/images/bg-top-phone.png" class="phone-main" />
<img src="/src/assets/images/bg-top-phone.png" class="phone-main anim-item" />
</div>
<div class="deco deco-coin1">🎁</div>
<div class="deco deco-coin2">🏆</div>
@@ -131,14 +131,14 @@
line-height: 1.3;
color: #1a1a1a;
margin-bottom: clamp(16px, 2vh, 40px);
animation: fadeIn 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.1s both;
animation: none;
}
.hero-buttons {
display: flex;
gap: clamp(10px, 3vw, 35px);
justify-content: center;
animation: fadeIn 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.25s both;
animation: none;
}
.btn-img {
@@ -196,7 +196,7 @@
height: clamp(330px, 58vh, 780px);
width: auto;
display: block;
animation: fadeIn 1s cubic-bezier(0.22, 1, 0.36, 1) 0.35s both;
animation: none;
}
@keyframes fadeIn {

View File

@@ -4,7 +4,7 @@
<div class="content-wrapper">
<div class="text-side">
<div class="title-wrap">
<h2 class="section-title">
<h2 class="section-title anim-item">
Customize Keyboard<br />
Define Chat Style
</h2>
@@ -12,13 +12,13 @@
<img src="/src/assets/images/two-2.png" class="deco-2" />
</div>
<div class="desc-wrap">
<p class="section-desc">
<p class="section-desc anim-item">
Spice up your keyboard with one-of-a-kind skins and customize its look to the fullestyour chat style is one-of-a-kind, and your keyboard should be too.
</p>
</div>
<div class="btns-wrap">
<div class="download-btns">
<div class="download-btns anim-item">
<a href="#"><img src="/src/assets/images/android-two.png" class="btn-img" /></a>
<a href="#"><img src="/src/assets/images/appStore-two.png" class="btn-img" /></a>
@@ -30,7 +30,7 @@
</div>
</div>
<img src="/src/assets/images/two-phone.png" class="two-phone" />
<img src="/src/assets/images/two-phone.png" class="two-phone anim-item" />
</section>
</template>