feat:增加返回顶部组件

This commit is contained in:
2026-06-08 16:31:20 +08:00
parent 21414245d9
commit 597159298f
3 changed files with 181 additions and 6 deletions

View File

@@ -0,0 +1,151 @@
<template>
<Transition name="fade">
<div class="back-top-container" v-show="showBackTop">
<svg class="progress-ring" viewBox="0 0 100 100">
<circle class="progress-ring-background" cx="50" cy="50" r="42" />
<circle
class="progress-ring-circle"
cx="50"
cy="50"
r="42"
:style="{'stroke-dashoffset': circumference - (scrollProgress * circumference)}"
/>
</svg>
<div
class="vitepress-backTop-main"
title="返回顶部"
@click="scrollToTop()"
>
<svg class="icon" viewBox="0 0 1024 1024">
<path d="M752.736 431.063C757.159 140.575 520.41 8.97 504.518 0.41V0l-0.45 0.205-0.41-0.205v0.41c-15.934 8.56-252.723 140.165-248.259 430.653-48.21 31.457-98.713 87.368-90.685 184.074 8.028 96.666 101.007 160.768 136.601 157.287 35.595-3.482 25.232-30.31 25.232-30.31l12.206-50.095s52.47 80.569 69.304 80.528c15.114-1.23 87-0.123 95.6 0h0.82c8.602-0.123 80.486-1.23 95.6 0 16.794 0 69.305-80.528 69.305-80.528l12.165 50.094s-10.322 26.83 25.272 30.31c35.595 3.482 128.574-60.62 136.602-157.286 8.028-96.665-42.475-152.617-90.685-184.074z m-248.669-4.26c-6.758-0.123-94.781-3.359-102.891-107.192 2.95-98.714 95.97-107.438 102.891-107.93 6.964 0.492 99.943 9.216 102.892 107.93-8.11 103.833-96.174 107.07-102.892 107.192z m-52.019 500.531c0 11.838-9.42 21.382-21.012 21.382a21.217 21.217 0 0 1-21.054-21.34V821.74c0-11.797 9.421-21.382 21.054-21.382 11.591 0 21.012 9.585 21.012 21.382v105.635z m77.333 57.222a21.504 21.504 0 0 1-21.34 21.626 21.504 21.504 0 0 1-21.34-21.626V827.474c0-11.96 9.543-21.668 21.299-21.668 11.796 0 21.38 9.708 21.38 21.668v157.082z m71.147-82.043c0 11.796-9.42 21.34-21.053 21.34a21.217 21.217 0 0 1-21.013-21.34v-75.367c0-11.755 9.421-21.299 21.013-21.299 11.632 0 21.053 9.544 21.053 21.3v75.366z" fill="#FFF"/>
</svg>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
const showBackTop = ref(false); // 初始状态设为false
const scrollProgress = ref(0);
// 圆形进度条计算
const radius = 42;
const circumference = computed(() => 2 * Math.PI * radius);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
// 使用更高效的节流函数
const throttle = (fn, delay = 50) => {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
const updateScrollProgress = () => {
const { scrollY, innerHeight } = window;
const { scrollHeight } = document.documentElement;
const totalScroll = scrollHeight - innerHeight;
scrollProgress.value = totalScroll > 0 ? Math.min(scrollY / totalScroll, 1) : 0;
};
const handleScroll = throttle(() => {
// 当滚动超过100px时显示否则隐藏
const shouldShow = window.scrollY > 100;
showBackTop.value = shouldShow;
updateScrollProgress();
});
onMounted(() => {
window.addEventListener("scroll", handleScroll);
updateScrollProgress();
});
onBeforeUnmount(() => {
window.removeEventListener("scroll", handleScroll);
});
</script>
<style scoped>
.back-top-container {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
z-index: 999;
}
.vitepress-backTop-main {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
width: 44px;
height: 44px;
border-radius: 50%;
background-color: #3eaf7c;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transition: background-color 0.2s ease;
}
.vitepress-backTop-main:hover {
background-color: #71cda3;
}
.progress-ring {
position: absolute;
width: 100%;
height: 100%;
transform: rotate(-90deg);
z-index: 1;
}
.progress-ring-background {
fill: none;
stroke: rgba(62, 175, 124, 0.15);
stroke-width: 3;
}
.progress-ring-circle {
fill: none;
stroke: #3eaf7c;
stroke-width: 3;
stroke-dasharray: 264; /* 2 * π * 42 */
stroke-linecap: round;
transition: stroke-dashoffset 0.15s ease-out;
}
.icon {
width: 24px;
height: 24px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -12,6 +12,10 @@
<template #doc-before>
<ArticleMetadat />
</template>
<template #doc-footer-before>
<Backtotop />
</template>
</DefaultTheme.Layout>
</div>
</Transition>
@@ -21,6 +25,7 @@
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import ArticleMetadat from './ArticleMetadata.vue';
import Backtotop from './Backtotop.vue';
import Loading from './Loading.vue';
import { ref, onMounted } from 'vue'

31
pnpm-lock.yaml generated
View File

@@ -35,10 +35,10 @@ importers:
version: 11.12.2
vitepress:
specifier: 1.6.4
version: 1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.6)(search-insights@2.17.3)
version: 1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.15)(search-insights@2.17.3)
vitepress-plugin-mermaid:
specifier: ^2.0.17
version: 2.0.17(mermaid@11.12.2)(vitepress@1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.6)(search-insights@2.17.3))
version: 2.0.17(mermaid@11.12.2)(vitepress@1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.15)(search-insights@2.17.3))
packages:
@@ -1069,6 +1069,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@3.3.12:
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
non-layered-tidy-tree-layout@2.0.2:
resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
@@ -1099,6 +1104,10 @@ packages:
points-on-path@0.2.1:
resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
postcss@8.5.15:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@@ -2397,6 +2406,9 @@ snapshots:
nanoid@3.3.11: {}
nanoid@3.3.12:
optional: true
non-layered-tidy-tree-layout@2.0.2:
optional: true
@@ -2429,6 +2441,13 @@ snapshots:
path-data-parser: 0.1.0
points-on-curve: 0.2.0
postcss@8.5.15:
dependencies:
nanoid: 3.3.12
picocolors: 1.1.1
source-map-js: 1.2.1
optional: true
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@@ -2593,14 +2612,14 @@ snapshots:
dependencies:
viewerjs: 1.11.7
vitepress-plugin-mermaid@2.0.17(mermaid@11.12.2)(vitepress@1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.6)(search-insights@2.17.3)):
vitepress-plugin-mermaid@2.0.17(mermaid@11.12.2)(vitepress@1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.15)(search-insights@2.17.3)):
dependencies:
mermaid: 11.12.2
vitepress: 1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.6)(search-insights@2.17.3)
vitepress: 1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.15)(search-insights@2.17.3)
optionalDependencies:
'@mermaid-js/mermaid-mindmap': 9.3.0
vitepress@1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.6)(search-insights@2.17.3):
vitepress@1.6.4(@algolia/client-search@5.45.0)(@types/node@24.10.1)(postcss@8.5.15)(search-insights@2.17.3):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.45.0)(search-insights@2.17.3)
@@ -2621,7 +2640,7 @@ snapshots:
vite: 5.4.21(@types/node@24.10.1)
vue: 3.5.25
optionalDependencies:
postcss: 8.5.6
postcss: 8.5.15
transitivePeerDependencies:
- '@algolia/client-search'
- '@types/node'