feat:完善其余页面
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
const progress = ref(0)
|
||||
const showBackToTop = ref(false)
|
||||
let observer
|
||||
let frame
|
||||
|
||||
function updateScrollState() {
|
||||
const scrollTop = window.scrollY || document.documentElement.scrollTop
|
||||
const scrollable = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1)
|
||||
progress.value = Math.min(100, Math.max(0, (scrollTop / scrollable) * 100))
|
||||
showBackToTop.value = scrollTop > Math.min(window.innerHeight * 0.72, 620)
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
if (frame) return
|
||||
frame = window.requestAnimationFrame(() => {
|
||||
frame = 0
|
||||
updateScrollState()
|
||||
})
|
||||
}
|
||||
|
||||
function prepareSectionReveals() {
|
||||
observer?.disconnect()
|
||||
const page = document.querySelector(
|
||||
'.about-page, .advantages-page, .services-page, .solutions-page, .cases-page, .case-detail-page, .insights-page, .article-page'
|
||||
)
|
||||
if (!page || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
|
||||
|
||||
page.classList.add('interior-reveal-ready')
|
||||
const sections = [
|
||||
...page.querySelectorAll('main > section, main > article > div, main > article > header')
|
||||
]
|
||||
sections.forEach((section, index) => {
|
||||
section.classList.add('interior-reveal')
|
||||
if (index === 0 || section.getBoundingClientRect().top < window.innerHeight * 0.92) {
|
||||
section.classList.add('is-revealed')
|
||||
}
|
||||
})
|
||||
|
||||
observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return
|
||||
entry.target.classList.add('is-revealed')
|
||||
observer?.unobserve(entry.target)
|
||||
})
|
||||
},
|
||||
{ rootMargin: '0px 0px -9% 0px', threshold: 0.08 }
|
||||
)
|
||||
|
||||
sections
|
||||
.filter((section) => !section.classList.contains('is-revealed'))
|
||||
.forEach((section) => observer.observe(section))
|
||||
}
|
||||
|
||||
function backToTop() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
window.addEventListener('resize', onScroll, { passive: true })
|
||||
await nextTick()
|
||||
updateScrollState()
|
||||
prepareSectionReveals()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
async () => {
|
||||
await nextTick()
|
||||
updateScrollState()
|
||||
prepareSectionReveals()
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
observer?.disconnect()
|
||||
if (frame) window.cancelAnimationFrame(frame)
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
window.removeEventListener('resize', onScroll)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="reading-progress" aria-hidden="true">
|
||||
<span :style="{ transform: `scaleX(${progress / 100})` }"></span>
|
||||
</div>
|
||||
<Transition name="back-top">
|
||||
<button
|
||||
v-if="showBackToTop"
|
||||
class="back-to-top"
|
||||
type="button"
|
||||
aria-label="回到页面顶部"
|
||||
title="回到顶部"
|
||||
@click="backToTop"
|
||||
>
|
||||
<AppIcon name="bottom" />
|
||||
</button>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.reading-progress {
|
||||
position: fixed;
|
||||
z-index: 45;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.reading-progress span {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
background: linear-gradient(90deg, #166ff3, #62c8f1);
|
||||
box-shadow: 0 1px 8px rgba(27, 124, 240, 0.34);
|
||||
transform-origin: left center;
|
||||
will-change: transform;
|
||||
}
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
z-index: 28;
|
||||
right: clamp(18px, 2.7vw, 42px);
|
||||
bottom: clamp(18px, 3vw, 40px);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(91, 151, 224, 0.22);
|
||||
border-radius: 6px;
|
||||
color: #2379e8;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 14px 34px rgba(37, 94, 160, 0.16);
|
||||
backdrop-filter: blur(14px);
|
||||
transition:
|
||||
color 0.22s ease,
|
||||
background 0.22s ease,
|
||||
transform 0.22s ease,
|
||||
box-shadow 0.22s ease;
|
||||
}
|
||||
.back-to-top :deep(.app-icon) {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.back-to-top:hover {
|
||||
color: #fff;
|
||||
background: #207bf2;
|
||||
box-shadow: 0 17px 36px rgba(31, 121, 236, 0.25);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
.back-to-top:active {
|
||||
transform: translateY(1px) scale(0.97);
|
||||
}
|
||||
.back-top-enter-active,
|
||||
.back-top-leave-active {
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
transform 0.24s ease;
|
||||
}
|
||||
.back-top-enter-from,
|
||||
.back-top-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
@media (max-width: 680px) {
|
||||
.back-to-top {
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user