进出动画效果
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 fullest—your 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user