BackTop 返回顶部
大约 3 分钟
概述
BackTop 是一个返回页面顶部的快捷按钮。
实现
props
传入的自定义按钮文本,默认为“顶部”- 使用
requestAnimationFrame()
替代直接赋值scrollTop = 0
,以获得更好的动画效果
<template>
<div class="back-top-btn">
<svg width="12" height="12">
<polygon points="0,12 12,12 6,6" fill="#FFF" stroke="none"></polygon>
</svg>
<span>{{ text }}</span>
</div>
</template>
<script>
export default {
props: {
text: {
type: String,
default: '顶部'
}
},
data() {
return {
showBtn: false
}
},
mounted() {
// 监听滚动事件,控制是否显示按钮
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
backTop() {
let timer = null
cancelAnimationFrame(timer)
timer = requestAnimationFrame(function scrollAnime () {
let distance = document.body.scrollTop || document.documentElement.scrollTop
if (distance > 0) { // 还未到达顶部
// 以 100px 为单位递归向上滚动
scrollTo(0, distance - 100)
timer = requestAnimationFrame(scrollAnime)
} else { // 已到达顶部,取消动画
cancelAnimationFrame(timer)
}
})
},
handleScroll() {
if (document.documentElement.scrollTop >= 400) {
this.showBtn = true
} else { // 卷起距离较短时,不显示按钮
this.showBtn = false
}
}
}
}
</script>
<style lang="scss" scoped>
.back-top-btn {
width: 48px;
position: fixed;
bottom: 120px;
left: 120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px 0;
font-size: 12px;
border: 2px solid #000;
user-select: none;
}
</style>
优化
以上基本实现了 BackTop 按钮的功能,但存在可以改进的地方。
为了方便大部分人的操作,BackTop 按钮通常会固定在页面的右下角。以上例子直接在组件上使用了 fixed
定位,考虑到我们可能会给 BackTop 按钮设置更为复杂的样式,不妨尝试将其定位属性抽离,在外层的 div
完成定位。
- back-top-outer:
fixed
定位,覆盖整个页面,设置pointer-events: none;
不触发事件,通过左右的padding
实现水平定位 - back-top-inner:
relative
定位,用于填充 back-top-outer - back-top-wrap:
absolute
定位,left: 100%
紧贴 back-top-inner,通过bottom
实现垂直定位 - back-top-btn:设置
pointer-events: auto;
可以触发事件
为了组件变得更美观,我们可以为 BackTop 按钮设置字体、背景、边框的样式,或是添加一个 fade 淡入淡出 动画。
最终代码如下。
<template>
<div id="back-top-btn">
<div v-show="showBtn" class="back-top-outer">
<div class="back-top-inner">
<div class="back-top-wrap">
<div class="back-top-btn" @click="backTop">
<svg width="12" height="12">
<polygon points="0,12 12,12 6,6" fill="#FFF" stroke="none"></polygon>
</svg>
<span>{{ text }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
text: {
type: String,
default: '顶部'
}
},
data() {
return {
showBtn: false
}
},
mounted() {
// 监听滚动事件,控制是否显示按钮
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
backTop() {
let timer = null
cancelAnimationFrame(timer)
timer = requestAnimationFrame(function scrollAnime () {
let distance = document.body.scrollTop || document.documentElement.scrollTop
if (distance > 0) { // 还未到达顶部
// 以 100px 为单位递归向上滚动
scrollTo(0, distance - 100)
timer = requestAnimationFrame(scrollAnime)
} else { // 已到达顶部,取消动画
cancelAnimationFrame(timer)
}
})
},
handleScroll() {
if (document.documentElement.scrollTop >= 400) {
this.showBtn = true
} else { // 卷起距离较短时,不显示按钮
this.showBtn = false
}
}
}
}
</script>
<style lang="scss" scoped>
.back-top-outer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0 96px;
background-color: transparent;
z-index: 1999;
pointer-events: none;
}
.back-top-inner {
position: relative;
width: 100%;
height: 100%;
}
.back-top-wrap {
position: absolute;
bottom: 120px;
left: 100%;
}
.back-top-btn {
width: 48px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px 0;
font-size: 12px;
color: #FFF;
background-color: #409EFF;
pointer-events: auto;
box-shadow: 0 2px 2px rgba(0, 0, 0, .08);
border: none;
border-radius: .5rem;
user-select: none;
animation: fade 0.2s linear forwards;
&:hover {
background-color: #66b1ff;
}
&:active {
background-color: #3a8ee6;
transform: scale(0.95);
}
}
@keyframes fade {
from { opacity: 0; }
to { opacity: 100%; }
}
</style>