进出动画效果
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, useSlots, watch, nextTick } from 'vue'
|
import { ref, computed, useSlots, nextTick } from 'vue'
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
const pageCount = computed(() => {
|
const pageCount = computed(() => {
|
||||||
@@ -35,21 +35,34 @@ const wrapperStyle = computed(() => ({
|
|||||||
function goTo(index) {
|
function goTo(index) {
|
||||||
if (isAnimating.value || index === currentIndex.value) return
|
if (isAnimating.value || index === currentIndex.value) return
|
||||||
if (index < 0 || index >= pageCount.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
|
isAnimating.value = true
|
||||||
|
triggerLeaveAnimation(prevIndex)
|
||||||
currentIndex.value = index
|
currentIndex.value = index
|
||||||
triggerPageAnimation(index)
|
triggerEnterAnimation(index)
|
||||||
setTimeout(() => { isAnimating.value = false }, 900)
|
setTimeout(() => { isAnimating.value = false }, 900)
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerPageAnimation(index) {
|
function triggerLeaveAnimation(index) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const pages = document.querySelectorAll('.fullpage-wrapper > *')
|
const pages = document.querySelectorAll('.fullpage-wrapper > *')
|
||||||
const page = pages[index]
|
const page = pages[index]
|
||||||
if (!page) return
|
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
|
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);
|
box-shadow: 0 0 8px rgba(0, 191, 165, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 页面入场动画 - 仅针对非绝对定位元素 */
|
/* 页面内元素进场/离场动画,通过 .anim-item 标记需要动画的元素 */
|
||||||
:global(.page-enter .text-side),
|
|
||||||
:global(.page-enter .image-side),
|
:global(.page-enter-down .anim-item) {
|
||||||
:global(.page-enter .content-wrapper) {
|
animation: enterFromBottom 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
animation: fadeSlideUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
|
animation-delay: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.page-enter .image-side) {
|
:global(.page-enter-up .anim-item) {
|
||||||
animation-delay: 0.1s;
|
animation: enterFromTop 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||||
|
animation-delay: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeSlideUp {
|
:global(.page-leave-up .anim-item) {
|
||||||
from {
|
animation: leaveToTop 0.32s cubic-bezier(0.76, 0, 0.24, 1) both;
|
||||||
opacity: 0;
|
}
|
||||||
transform: translateY(32px);
|
|
||||||
}
|
:global(.page-leave-down .anim-item) {
|
||||||
to {
|
animation: leaveToBottom 0.32s cubic-bezier(0.76, 0, 0.24, 1) both;
|
||||||
opacity: 1;
|
}
|
||||||
transform: translateY(0);
|
|
||||||
}
|
/* 子元素依次错开出现(仅进场) */
|
||||||
|
: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) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
<section class="page page-advantage">
|
<section class="page page-advantage">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="image-side">
|
<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>
|
||||||
<div class="text-side">
|
<div class="text-side">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title anim-item">
|
||||||
Every opening is an<br />
|
Every opening is an<br />
|
||||||
unknown encounter.
|
unknown encounter.
|
||||||
</h2>
|
</h2>
|
||||||
<div class="section-card">
|
<div class="section-card anim-item">
|
||||||
<img src="/src/assets/images/three-douhao.png" class="douhao-img">
|
<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.
|
Every moment you open it, you embark on a poetic encounter with the unknown, and meet the unexpected beauty.
|
||||||
</p>
|
</p>
|
||||||
<div class="hero-buttons">
|
<div class="hero-buttons">
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
<section class="page page-contact">
|
<section class="page page-contact">
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="text-side">
|
<div class="text-side">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title anim-item">
|
||||||
Customize Keyboard<br />
|
Customize Keyboard<br />
|
||||||
AI Reply .
|
AI Reply .
|
||||||
</h2>
|
</h2>
|
||||||
<p class="section-desc">
|
<p class="section-desc anim-item">
|
||||||
Personalize your chat keyboard and get tailored, high-EQ responses for every conversation
|
Personalize your chat keyboard and get tailored, high-EQ responses for every conversation
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-side">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
<div class="hero">
|
<div class="hero">
|
||||||
<div class="hero-phones">
|
<div class="hero-phones">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
<h1 class="hero-title">
|
<h1 class="hero-title anim-item">
|
||||||
Not just a tool but<br />
|
Not just a tool but<br />
|
||||||
giving meaning to every click
|
giving meaning to every click
|
||||||
</h1>
|
</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/android.png" class="btn-img" /></a>
|
||||||
<a href="#"><img src="/src/assets/images/appStore.png" class="btn-img" /></a>
|
<a href="#"><img src="/src/assets/images/appStore.png" class="btn-img" /></a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<div class="deco deco-coin1">🎁</div>
|
<div class="deco deco-coin1">🎁</div>
|
||||||
<div class="deco deco-coin2">🏆</div>
|
<div class="deco deco-coin2">🏆</div>
|
||||||
@@ -131,14 +131,14 @@
|
|||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
color: #1a1a1a;
|
color: #1a1a1a;
|
||||||
margin-bottom: clamp(16px, 2vh, 40px);
|
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 {
|
.hero-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: clamp(10px, 3vw, 35px);
|
gap: clamp(10px, 3vw, 35px);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
animation: fadeIn 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.25s both;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-img {
|
.btn-img {
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
height: clamp(330px, 58vh, 780px);
|
height: clamp(330px, 58vh, 780px);
|
||||||
width: auto;
|
width: auto;
|
||||||
display: block;
|
display: block;
|
||||||
animation: fadeIn 1s cubic-bezier(0.22, 1, 0.36, 1) 0.35s both;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="text-side">
|
<div class="text-side">
|
||||||
<div class="title-wrap">
|
<div class="title-wrap">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title anim-item">
|
||||||
Customize Keyboard<br />
|
Customize Keyboard<br />
|
||||||
Define Chat Style
|
Define Chat Style
|
||||||
</h2>
|
</h2>
|
||||||
@@ -12,13 +12,13 @@
|
|||||||
<img src="/src/assets/images/two-2.png" class="deco-2" />
|
<img src="/src/assets/images/two-2.png" class="deco-2" />
|
||||||
</div>
|
</div>
|
||||||
<div class="desc-wrap">
|
<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.
|
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>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="btns-wrap">
|
<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/android-two.png" class="btn-img" /></a>
|
||||||
<a href="#"><img src="/src/assets/images/appStore-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>
|
||||||
</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>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user