渐显动画
This commit is contained in:
@@ -54,6 +54,11 @@ function handleNavigate(index) {
|
||||
align-items: center;
|
||||
padding: 0 @nav-px;
|
||||
z-index: 1000;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
backdrop-filter: blur(16px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
</div>
|
||||
<div class="fullpage-dots">
|
||||
<span v-for="i in pageCount" :key="i" class="dot" :class="{ active: currentIndex === i - 1 }"
|
||||
@click="goTo(i - 1)" />
|
||||
@click="goTo(i - 1)">
|
||||
<span class="dot-inner" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import { ref, computed, useSlots, watch, nextTick } from 'vue'
|
||||
|
||||
const slots = useSlots()
|
||||
const pageCount = computed(() => {
|
||||
@@ -23,29 +25,38 @@ const pageCount = computed(() => {
|
||||
const currentIndex = ref(0)
|
||||
const isAnimating = ref(false)
|
||||
const touchStartY = ref(0)
|
||||
const direction = ref('down')
|
||||
|
||||
const wrapperStyle = computed(() => ({
|
||||
transform: `translateY(-${currentIndex.value * 100}vh)`,
|
||||
transition: isAnimating.value ? 'transform 0.8s cubic-bezier(0.65, 0, 0.35, 1)' : 'none',
|
||||
transition: isAnimating.value ? 'transform 0.9s cubic-bezier(0.76, 0, 0.24, 1)' : 'none',
|
||||
}))
|
||||
|
||||
function goTo(index) {
|
||||
if (isAnimating.value || index === currentIndex.value) return
|
||||
if (index < 0 || index >= pageCount.value) return
|
||||
direction.value = index > currentIndex.value ? 'down' : 'up'
|
||||
isAnimating.value = true
|
||||
currentIndex.value = index
|
||||
setTimeout(() => {
|
||||
isAnimating.value = false
|
||||
}, 800)
|
||||
triggerPageAnimation(index)
|
||||
setTimeout(() => { isAnimating.value = false }, 900)
|
||||
}
|
||||
|
||||
function triggerPageAnimation(index) {
|
||||
nextTick(() => {
|
||||
const pages = document.querySelectorAll('.fullpage-wrapper > *')
|
||||
const page = pages[index]
|
||||
if (!page) return
|
||||
page.classList.remove('page-enter')
|
||||
void page.offsetWidth
|
||||
page.classList.add('page-enter')
|
||||
})
|
||||
}
|
||||
|
||||
function handleWheel(e) {
|
||||
if (isAnimating.value) return
|
||||
if (e.deltaY > 0) {
|
||||
goTo(currentIndex.value + 1)
|
||||
} else if (e.deltaY < 0) {
|
||||
goTo(currentIndex.value - 1)
|
||||
}
|
||||
if (e.deltaY > 0) goTo(currentIndex.value + 1)
|
||||
else if (e.deltaY < 0) goTo(currentIndex.value - 1)
|
||||
}
|
||||
|
||||
function handleTouchStart(e) {
|
||||
@@ -55,11 +66,8 @@ function handleTouchStart(e) {
|
||||
function handleTouchEnd(e) {
|
||||
const deltaY = touchStartY.value - e.changedTouches[0].clientY
|
||||
if (Math.abs(deltaY) > 50) {
|
||||
if (deltaY > 0) {
|
||||
goTo(currentIndex.value + 1)
|
||||
} else {
|
||||
goTo(currentIndex.value - 1)
|
||||
}
|
||||
if (deltaY > 0) goTo(currentIndex.value + 1)
|
||||
else goTo(currentIndex.value - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,17 +87,83 @@ defineExpose({ currentIndex, goTo })
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* 圆点导航升级 */
|
||||
.fullpage-dots {
|
||||
position: fixed;
|
||||
right: 30px;
|
||||
right: 28px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dot::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.dot-inner {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dot.active::before {
|
||||
border-color: #00BFA5;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-shadow: 0 0 0 3px rgba(0, 191, 165, 0.15);
|
||||
}
|
||||
|
||||
.dot.active .dot-inner {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #00BFA5;
|
||||
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;
|
||||
}
|
||||
|
||||
:global(.page-enter .image-side) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
@keyframes fadeSlideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(32px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.fullpage-dots {
|
||||
right: unset;
|
||||
@@ -99,22 +173,9 @@ defineExpose({ currentIndex, goTo })
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dot.active {
|
||||
background: #00BFA5;
|
||||
border-color: #00BFA5;
|
||||
transform: scale(1.3);
|
||||
box-shadow: 0 0 8px rgba(0, 191, 165, 0.4);
|
||||
.dot.active::before {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -131,21 +131,24 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.btn-img {
|
||||
height: clamp(36px, 6.6vh, 120px);
|
||||
width: auto;
|
||||
display: block;
|
||||
transition: transform 0.3s;
|
||||
transition: transform 0.3s, filter 0.3s;
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
transform: translateY(-3px) scale(1.04);
|
||||
filter: drop-shadow(0 8px 16px rgba(0,0,0,0.15));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +196,17 @@
|
||||
height: clamp(330px, 58vh, 780px);
|
||||
width: auto;
|
||||
display: block;
|
||||
animation: fadeIn 1s cubic-bezier(0.22, 1, 0.36, 1) 0.35s both;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadeSlideUp {
|
||||
from { opacity: 0; transform: translateY(40px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.phone-placeholder {
|
||||
|
||||
Reference in New Issue
Block a user