文章摘要
白衣 DeepSeek

前言

在网页开发中,一个实用且美观的返回顶部按钮能极大提升用户浏览长页面时的便捷性。白衣偶然在一个国外网站看到一个这种返回顶部按钮,于是便动手仿写了一个。接下来,我将逐步展示如何构建一个丝滑且带有进度条显示的返回顶部

## 一.构建按钮 首先,构建按钮的 HTML 结构,创建一个**按钮**元素,在按钮内部添加**色块填充**一个用于显示进度条,加一个返回顶部的**箭头图标**
1
2
3
4
5
6
7
8
 <button onclick="topFunction()" id="myBtn" class="baiyi_fn_totop" title="回顶部">
<span class="progress_wrapper">
<span class="progress" style="height: 0%;"></span>
</span>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px"
y="0px" viewBox="0 0 557.97 1061" style="enable-background:new 0 0 557.97 1061;" xml:space="preserve"
class="fn__svg replaced-svg"><path d="M557.97,554.2c0,4.13,0,8.26,0,12.39c-0.37,2.83-0.75,5.66-1.11,8.49c-2.47,19.12-10.14,35.74-23.81,49.46 c-1.02,1.02-2.42,1.87-3.79,2.31c-8.96,2.89-18.12,3.28-27.38,1.91c-13.68-2.03-26.18-7.3-38.1-14.08 c-21.62-12.31-40.29-28.39-57.82-45.86c-28.44-28.34-53.31-59.63-76.03-92.68c-0.61-0.89-1.29-1.73-2.3-3.08 c-0.1,1.2-0.17,1.63-0.17,2.06c0.03,32.31-0.98,64.6-3.29,96.83c-1.56,21.73-3.32,43.46-5.57,65.13 c-2.71,26.12-6.58,52.09-11.66,77.88c-6.4,32.52-15.07,64.34-28.3,94.82c-22.74,52.42-51.12,101.67-83.67,148.55 c-17.42,25.09-35.96,49.32-57.81,70.77c-14.1,13.84-28.57,27.36-48.82,31.92c-1.97,0-3.95,0-5.92,0c-3.98-1.33-7.87-2.73-10.95-5.86 c-5.86-5.95-8.47-13.2-8.88-21.34c-0.68-13.44,2.95-26.11,7.27-38.6c7.44-21.55,17.29-42.09,27.01-62.67 c14.89-31.52,29.92-62.97,43.1-95.26c17.4-42.65,30.2-86.59,37.18-132.14c3.19-20.81,5.97-41.71,8.22-62.64 c2.09-19.52,3.54-39.12,4.7-58.73c1.05-17.73,1.42-35.5,1.92-53.26c0.95-34.02,0.14-68.02-1.36-102c-0.16-3.52-0.46-7.03-0.7-10.54 c-1.06,0.87-1.57,1.78-2.04,2.72c-18.76,36.7-39.4,72.27-63.39,105.83c-13.17,18.43-27.28,36.08-44.08,51.41 c-9.45,8.62-19.55,16.34-31.6,21.09c-7.23,2.86-14.69,4.16-22.4,2.77c-13.71-2.47-20.98-11.51-24.1-24.42 c-1.07-4.42-1.54-8.97-2.29-13.47c0-4.49,0-8.98,0-13.46c0.18-0.86,0.42-1.72,0.53-2.59c1.04-8.27,1.79-16.58,3.14-24.79 c3.57-21.68,9.15-42.91,15.05-64.05c9.97-35.71,20.36-71.3,30.05-107.09c9.64-35.6,17.21-71.58,18.86-108.63 c1.27-28.54,0.95-57.05,0.09-85.58c-0.86-28.33-2.7-56.65-1.36-85.02c0.56-11.84,1.68-23.63,4.84-35.09 C75.99,20.25,84.24,5.72,102.87,0c1.8,0,3.59,0,5.39,0c5.02,1.57,10.2,2.77,15.04,4.79c10.07,4.19,19.11,10.23,27.92,16.58 c22.42,16.14,42.81,34.66,62.61,53.83c49.96,48.37,96.11,100.26,141.08,153.24c9.64,11.35,18.66,23.27,28.83,34.13 c38.53,41.13,74.39,84.4,106,131.1c19.59,28.93,37.26,58.96,50.61,91.35c7.53,18.28,13.53,37.01,16.13,56.7 C557.02,545.87,557.47,550.04,557.97,554.2z"></path></svg>
</button>

二.编写CSS样式

通过transform: translateY(120px)让按钮初始处于隐藏状态,当添加 active 类时显示。同时,设置z-index确保箭头图标在进度色块之上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* Author:BAIYI */
.baiyi_fn_totop.active {
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}

@media (max-width: 1040px) {
.baiyi_fn_totop {
right: 30px;
bottom: 36px;
}
}

.baiyi_fn_totop {
position: fixed;
z-index: 15;
bottom: 46px;
right: 40px;
text-decoration: none;
width: 50px;
height: 50px;
border: 4px solid #000;
border-radius: 20px;
background-color: #fff;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-align-items: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
cursor: pointer;
-webkit-transform: translateY(120px);
-ms-transform: translateY(120px);
transform: translateY(120px);
}

.baiyi_fn_totop:after {
content: "";
position: absolute;
top: 0;
left: -4px;
right: -4px;
bottom: -10px;
z-index: -1;
border-radius: 0 0 25px 25px;
border-bottom: 10px solid #000;
}

.baiyi_fn_totop svg {
width: 20px;
height: 20px;
z-index: 2; /* 确保箭头图标的 z-index 高于进度条 */
}

/* 进度条的样式 */
.progress_wrapper {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 16px;
overflow: hidden;
}

.progress {
display: block;
width: 100%;
height: 0%;
background-color: #f9d40d;
transition: height 0.5s ease-in-out;
z-index: 1; /* 调整进度条的 z-index,使其位于箭头图标下方 */
}

三.JS平滑与进度监听

通过JavaScript进行节流、滚动监听、进度更新以及缓动动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//1.节流函数-BAIYI
let rafId;
let lastCall = 0;

function throttle(func, limit) {
return function() {
const now = new Date().getTime();
if (now - lastCall < limit) {
return;
}
lastCall = now;
return func.apply(this, arguments);
};
}

//2.更新进度条与按钮显示状态-BAIYI
function updateProgress() {
// 计算进度条高度
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const progress = Math.floor((scrollTop / scrollHeight) * 100); // 使用 Math.floor 取整
const progressBar = document.querySelector('.progress');
progressBar.style.height = `${progress}%`;

// 当网页向下滑动 20px 出现"返回顶部" 按钮
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
document.getElementById("myBtn").classList.add('active');
} else {
document.getElementById("myBtn").classList.remove('active');
}

rafId = window.requestAnimationFrame(updateProgress);
}
//3.滚动监听控制-BAIYI
function startScrollListener() {
updateProgress();
}

function stopScrollListener() {
window.cancelAnimationFrame(rafId);
}
//4.平滑滚动到顶部-BAIYI
// 点击按钮,返回顶部
function topFunction() {
const startY = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
const startTime = performance.now();
const duration = 500; // 滚动持续时间,单位为毫秒,可根据需要调整

function scrollToTop(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = easeInOutCubic(progress); // 使用缓动函数
const newY = startY * (1 - ease);

document.body.scrollTop = newY;
document.documentElement.scrollTop = newY;

if (progress < 1) {
window.requestAnimationFrame(scrollToTop);
} else {
stopScrollListener();
}
}

window.requestAnimationFrame(scrollToTop);
}

// 缓动函数,这里我使用的是 easeInOutCubic 函数,可以根据需要替换为其他缓动函数
function easeInOutCubic(t) {
return t < 0.5? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}
//5.事件绑定-BAIYI
// 使用 throttle 函数对 updateProgress 进行节流
const throttledUpdateProgress = throttle(updateProgress, 10);

// 开始监听滚动事件
window.addEventListener('scroll', throttledUpdateProgress);

// 监听触摸事件,使在触摸设备上也能正常工作
document.addEventListener('touchstart', function (e) {
startScrollListener();
});
document.addEventListener('touchmove', function (e) {
throttledUpdateProgress();
});
document.addEventListener('touchend', function (e) {
stopScrollListener();
});
功能模块 实现方式 作用
节流控制 定义throttle函数,依上次调用时间与间隔控制频率 updateProgress频繁触发,优化性能
进度条更新与按钮显示 updateProgress依滚动距离、总高度算进度更新进度条,依滚动距离控按钮显隐 实时展示进度,依滚动位置控按钮
滚动监听管理 startScrollListenerupdateProgress开启监听,stopScrollListenercancelAnimationFrame停止 控滚动监听开关,合理用资源
平滑滚动到顶部 topFunction结合easeInOutCubic缓动函数与requestAnimationFrame算滚动位置更新 点击按钮时页面平滑滚至顶部,提用户体验
触摸事件支持 为文档添touchstarttouchmovetouchend事件监听器,分别调用对应函数 确保触摸设备正常实现滚动监听与进度更新等交互

四.完整代码

下面给出所有融合在一起的代码,复制即可使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<style>
.baiyi_fn_totop.active {
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}
@media (max-width: 1040px) {
.baiyi_fn_totop {
right: 30px;
bottom: 36px;
}
}
.baiyi_fn_totop {
position: fixed;
z-index: 15;
bottom: 46px;
right: 40px;
text-decoration: none;
width: 50px;
height: 50px;
border: 4px solid #000;
border-radius: 20px;
background-color: #fff;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-align-items: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
cursor: pointer;
-webkit-transform: translateY(120px);
-ms-transform: translateY(120px);
transform: translateY(120px);
}
.baiyi_fn_totop:after {
content: "";
position: absolute;
top: 0;
left: -4px;
right: -4px;
bottom: -10px;
z-index: -1;
border-radius: 0 0 25px 25px;
border-bottom: 10px solid #000;
}
.baiyi_fn_totop svg {
width: 20px;
height: 20px;
z-index: 2; /* 确保箭头图标的 z-index 高于进度条 */
}
/* 进度条的样式 */
.progress_wrapper {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 16px;
overflow: hidden;
}
.progress {
display: block;
width: 100%;
height: 0%;
background-color: #f9d40d;
transition: height 0.5s ease-in-out;
z-index: 1; /* 调整进度条的 z-index,使其位于箭头图标下方 */
}
</style>
<button onclick="topFunction()" id="myBtn" class="baiyi_fn_totop" title="回顶部">
<span class="progress_wrapper">
<span class="progress" style="height: 0%;"></span>
</span>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px"
y="0px" viewBox="0 0 557.97 1061" style="enable-background:new 0 0 557.97 1061;" xml:space="preserve"
class="fn__svg replaced-svg"><path d="M557.97,554.2c0,4.13,0,8.26,0,12.39c-0.37,2.83-0.75,5.66-1.11,8.49c-2.47,19.12-10.14,35.74-23.81,49.46 c-1.02,1.02-2.42,1.87-3.79,2.31c-8.96,2.89-18.12,3.28-27.38,1.91c-13.68-2.03-26.18-7.3-38.1-14.08 c-21.62-12.31-40.29-28.39-57.82-45.86c-28.44-28.34-53.31-59.63-76.03-92.68c-0.61-0.89-1.29-1.73-2.3-3.08 c-0.1,1.2-0.17,1.63-0.17,2.06c0.03,32.31-0.98,64.6-3.29,96.83c-1.56,21.73-3.32,43.46-5.57,65.13 c-2.71,26.12-6.58,52.09-11.66,77.88c-6.4,32.52-15.07,64.34-28.3,94.82c-22.74,52.42-51.12,101.67-83.67,148.55 c-17.42,25.09-35.96,49.32-57.81,70.77c-14.1,13.84-28.57,27.36-48.82,31.92c-1.97,0-3.95,0-5.92,0c-3.98-1.33-7.87-2.73-10.95-5.86 c-5.86-5.95-8.47-13.2-8.88-21.34c-0.68-13.44,2.95-26.11,7.27-38.6c7.44-21.55,17.29-42.09,27.01-62.67 c14.89-31.52,29.92-62.97,43.1-95.26c17.4-42.65,30.2-86.59,37.18-132.14c3.19-20.81,5.97-41.71,8.22-62.64 c2.09-19.52,3.54-39.12,4.7-58.73c1.05-17.73,1.42-35.5,1.92-53.26c0.95-34.02,0.14-68.02-1.36-102c-0.16-3.52-0.46-7.03-0.7-10.54 c-1.06,0.87-1.57,1.78-2.04,2.72c-18.76,36.7-39.4,72.27-63.39,105.83c-13.17,18.43-27.28,36.08-44.08,51.41 c-9.45,8.62-19.55,16.34-31.6,21.09c-7.23,2.86-14.69,4.16-22.4,2.77c-13.71-2.47-20.98-11.51-24.1-24.42 c-1.07-4.42-1.54-8.97-2.29-13.47c0-4.49,0-8.98,0-13.46c0.18-0.86,0.42-1.72,0.53-2.59c1.04-8.27,1.79-16.58,3.14-24.79 c3.57-21.68,9.15-42.91,15.05-64.05c9.97-35.71,20.36-71.3,30.05-107.09c9.64-35.6,17.21-71.58,18.86-108.63 c1.27-28.54,0.95-57.05,0.09-85.58c-0.86-28.33-2.7-56.65-1.36-85.02c0.56-11.84,1.68-23.63,4.84-35.09 C75.99,20.25,84.24,5.72,102.87,0c1.8,0,3.59,0,5.39,0c5.02,1.57,10.2,2.77,15.04,4.79c10.07,4.19,19.11,10.23,27.92,16.58 c22.42,16.14,42.81,34.66,62.61,53.83c49.96,48.37,96.11,100.26,141.08,153.24c9.64,11.35,18.66,23.27,28.83,34.13 c38.53,41.13,74.39,84.4,106,131.1c19.59,28.93,37.26,58.96,50.61,91.35c7.53,18.28,13.53,37.01,16.13,56.7 C557.02,545.87,557.47,550.04,557.97,554.2z"></path></svg>
</button>
<script>
let rafId;
let lastCall = 0;
function throttle(func, limit) {
return function() {
const now = new Date().getTime();
if (now - lastCall < limit) {
return;
}
lastCall = now;
return func.apply(this, arguments);
};
}
function updateProgress() {
// 计算进度条高度
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const progress = Math.floor((scrollTop / scrollHeight) * 100); // 使用 Math.floor 取整
const progressBar = document.querySelector('.progress');
progressBar.style.height = `${progress}%`;
// 当网页向下滑动 20px 出现"返回顶部" 按钮
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
document.getElementById("myBtn").classList.add('active');
} else {
document.getElementById("myBtn").classList.remove('active');
}
rafId = window.requestAnimationFrame(updateProgress);
}
function startScrollListener() {
updateProgress();
}
function stopScrollListener() {
window.cancelAnimationFrame(rafId);
}
// 点击按钮,返回顶部
function topFunction() {
const startY = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
const startTime = performance.now();
const duration = 500; // 滚动持续时间,单位为毫秒,可根据需要调整
function scrollToTop(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = easeInOutCubic(progress); // 使用缓动函数
const newY = startY * (1 - ease);
document.body.scrollTop = newY;
document.documentElement.scrollTop = newY;
if (progress < 1) {
window.requestAnimationFrame(scrollToTop);
} else {
stopScrollListener();
}
}
window.requestAnimationFrame(scrollToTop);
}
// 缓动函数,这里使用的是 easeInOutCubic 函数,可以根据需要替换为其他缓动函数
function easeInOutCubic(t) {
return t < 0.5? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}
// 使用 throttle 函数对 updateProgress 进行节流
const throttledUpdateProgress = throttle(updateProgress, 10);
// 开始监听滚动事件
window.addEventListener('scroll', throttledUpdateProgress);
// 监听触摸事件,使在触摸设备上也能正常工作
document.addEventListener('touchstart', function (e) {
startScrollListener();
});
document.addEventListener('touchmove', function (e) {
throttledUpdateProgress();
});
document.addEventListener('touchend', function (e) {
stopScrollListener();
});
</script>

通过以上代码,我们就成功地实现了一个丝滑的网页返回顶部样式,你可以根据自己的喜好进一步调整样式和功能,让它更好地融入你的网页设计中。