业务背景
一个页面以tab栏作为区块划分时,tab栏与页面彼此间的联动交互,往往能提供更好的体验:点击tab栏时页面滚动到tab栏对应的页面区域,同时滚动页面时tab栏随之改变。
本文基于VUE2.0实现此功能,图例如下:
HTML部分
<!-- 使用Affix(antd affix)组件固定tab在顶部48px位置上 -->
<!-- getTargetEle确定固定目标元素(需与页面滚动元素一致) -->
<div class="a-container">
<Affix :offset-top="48" :target="getIdEle">
<div class="a-tabbar">
<div
v-for="(item, index) in tabList"
:key="item"
class="a-tabbar-item"
:class="activeKey === item ? 'a-tabbar-item-active' : ''"
@click="handleTabClick(item, index)"
>
<div class="a-tabbar-item-title">tab栏{{ index }}</div>
<!-- 分割线显示 -->
<div v-if="index < tabList.length - 1" class="a-tabbar-item-split"></div>
</div>
</div>
</Affix>
<div class="a-content">
<!-- 页面主体 -->
<div
class="a-content-item"
:class="activeKey === item ? 'a-content-item-active' : ''"
v-for="(item, index) in tabPaneList"
:key="item"
>
<div class="a-content-item-title">tab区域{{ index }}</div>
<div style="height: 120px"></div>
</div>
</div>
<!-- 返回顶部按钮显示 -->
<div class="backToTop" @click="backToTop" v-if="showScrollTop">
<i class="icon iconfont icon-fanhuidingbu"></i>
</div>
</div>
注:getTargetEle一般不需格外指定,默认为window,本文因样式问题,指定为VUE根DOM——app
JS部分
export default {
// 引入固钉组件
components: {
Affix,
},
data() {
return {
activeKey: "1",
tabList: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
tabPaneList: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
positionList: [],
showScrollTop: false,
};
},
mounted() {
// 页面挂载阶段添加滚动事件、获取滚动位置
document.getElementById("app").addEventListener("scroll", this.handleScroll);
this.getScroll()
},
methods: {
getScroll() {
setTimeout(() => {
// 重置定位列表
this.positionList = [];
const contentList = document.getElementsByClassName("a-content-item");
const container = document.getElementsByClassName("a-container")[0];
// 获取默认高度
const defaultTop = (container.offsetTop ? container.offsetTop : 0) - 61;
Array.from(contentList).forEach((item) => {
this.positionList.push(item.offsetTop + defaultTop);
});
});
},
// tab栏点击时指定页面滚动位置
handleTabClick(key, index) {
document.getElementById("app").scrollTop = this.positionList[index];
this.activeKey = key;
},
// 返回顶部
backToTop() {
document.getElementById("app").scrollTo({
top: 0,
behavior: "smooth",
});
},
handleScroll(e) {
if (e.target.id === "app") {
const scrollTop = e.target.scrollTop;
this.showScrollTop = scrollTop > this.positionList[2];
// 获取就近tab activeKey
const findNearIndex = (sT) => {
let min = null;
let index = 0;
this.positionList.forEach((item, i) => {
const distance = Math.abs(sT - item);
if (min) {
if (distance < min) {
min = distance;
index = i;
}
} else {
min = distance;
}
});
return index;
};
const index = findNearIndex(scrollTop);
this.activeKey = this.tabList[index];
}
},
getIdEle() {
return document.getElementById("app");
},
},
beforeDestroy() {
// 页面注销之前,移除滚动事件
document.getElementById("app").removeEventListener("scroll", this.handleScroll);
},
}
本文以VUE根DOM为滚动参考,页面未做特殊配置时,handleScroll事件中前两行写法
if (e.target.id === "app") {
const scrollTop = e.target.scrollTop;↓
if (e.target.scrollingElement.nodeName === "HTML") {
let scrollTop = e.target.scrollingElement.scrollTop;
handleTabClick事件代码需替换
document.getElementById("app").scrollTop = this.positionList[index];↓
document.documentElement.scrollTop = this.positionList[index];
注: getScroll函数中defaultTop为此功能组件的默认高度,引入组件的位置有时并非在页面最顶端,需要获取默认高度。61为系统顶部菜单栏高度,若系统中无顶部菜单,可忽略
CSS部分
.a-tabbar {
z-index: 2;
display: flex;
background-color: #fff;
padding: 20px 0;
}
.a-tabbar-item {
font-family: PingFangSC-Regular;
font-size: 14px;
color: #4e5969;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
color: #3270ff;
}
.a-tabbar-item-split {
width: 1px;
margin: 0 12px;
height: 15px;
background-color: rgba(184, 188, 196, 1);
}
}
.a-tabbar-item-active {
color: #3270ff;
}
.a-content {
padding-top: 0;
}
.a-content-item {
margin-top: 32px;
.a-content-item-title {
font-family: PingFangSC-Medium;
font-size: 16px;
color: #1d2129;
line-height: 22px;
font-weight: 600;
margin-bottom: 16px;
}
}
.a-content-item:first-child {
margin-top: 12px;
}
.a-container {
position: relative;
}
.backToTop {
width: 40px;
height: 40px;
background: #ffffff;
box-shadow: 0px 0px 14px 0px rgba(0, 0, 0, 0.11);
border-radius: 27px;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
right: 32px;
bottom: 85px;
z-index: 1;
}
总结
具体实现时,还需注意tab中数据是否为异步加载,以及特殊操作时造成的tab栏与tabpane区域的变动。针对此问题的解决方案为,异步加载的回调函数(promise对象的then属性等)中,或者在特殊操作之后调用getScroll函数,重新获取tab项的定位列表。