回到顶部按钮
添加一个平滑滚动的回到顶部按钮,提升长页面的用户体验。
组件实现
vue
<!-- .vitepress/theme/components/BackToTop.vue -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const visible = ref(false)
const scrollY = ref(0)
const handleScroll = () => {
scrollY.value = window.scrollY
visible.value = window.scrollY > 300
}
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
handleScroll()
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
</script>
<template>
<Transition name="fade">
<button
v-show="visible"
class="back-to-top"
@click="scrollToTop"
aria-label="返回顶部"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
</Transition>
</template>
<style scoped>
.back-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
z-index: 99;
}
.back-to-top:hover {
background: var(--vp-c-brand-1);
color: white;
border-color: var(--vp-c-brand-1);
transform: translateY(-4px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.back-to-top:active {
transform: translateY(-2px);
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(20px);
}
/* 深色模式适配 */
.dark .back-to-top {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
</style>注册组件
ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import BackToTop from './components/BackToTop.vue'
import { h } from 'vue'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
'layout-bottom': () => h(BackToTop)
})
}
}高级:带进度指示
vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
const visible = ref(false)
const scrollY = ref(0)
const docHeight = ref(0)
const progress = computed(() => {
if (docHeight.value === 0) return 0
return Math.min(scrollY.value / (docHeight.value - window.innerHeight), 1)
})
const handleScroll = () => {
scrollY.value = window.scrollY
docHeight.value = document.documentElement.scrollHeight
visible.value = window.scrollY > 300
}
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
handleScroll()
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
</script>
<template>
<Transition name="fade">
<button
v-show="visible"
class="back-to-top"
@click="scrollToTop"
aria-label="返回顶部"
>
<svg viewBox="0 0 36 36" class="progress-ring">
<circle
class="progress-ring-bg"
cx="18"
cy="18"
r="16"
/>
<circle
class="progress-ring-progress"
cx="18"
cy="18"
r="16"
:stroke-dasharray="`${progress * 100} 100`"
/>
</svg>
<svg class="arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
</Transition>
</template>
<style scoped>
.back-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--vp-c-bg);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 99;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.progress-ring {
position: absolute;
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.progress-ring-bg {
fill: none;
stroke: var(--vp-c-divider);
stroke-width: 2;
}
.progress-ring-progress {
fill: none;
stroke: var(--vp-c-brand-1);
stroke-width: 2;
stroke-linecap: round;
transition: stroke-dasharray 0.1s;
}
.arrow {
position: relative;
color: var(--vp-c-text-1);
}
.back-to-top:hover .arrow {
color: var(--vp-c-brand-1);
}
</style>可配置选项
vue
<script setup lang="ts">
interface Props {
threshold?: number // 显示阈值
bottom?: string // 底部距离
right?: string // 右侧距离
size?: 'sm' | 'md' | 'lg' // 按钮大小
}
const props = withDefaults(defineProps<Props>(), {
threshold: 300,
bottom: '2rem',
right: '2rem',
size: 'md'
})
const sizeMap = {
sm: '36px',
md: '44px',
lg: '52px'
}
</script>
<template>
<button
class="back-to-top"
:style="{
bottom: props.bottom,
right: props.right,
width: sizeMap[props.size],
height: sizeMap[props.size]
}"
>
<!-- ... -->
</button>
</template>使用示例
vue
<!-- 使用默认配置 -->
<BackToTop />
<!-- 自定义配置 -->
<BackToTop
:threshold="500"
bottom="3rem"
right="1.5rem"
size="lg"
/>