需求背景
最近也是应我一位同学的要求,给他的公司制作一个官网,那也就是拾起了一些基础的知识,搜索了一些其它的文章有实现这种效果的,但大多导航都是在侧边,然后点击导航对应的部分内容滑到最上面,跟我这次设计的交互有些偏差,所以自己也是实现了一把,比较简单,大体逻辑跟其它的比较类似。
要实现的效果预览
- 导航固定在顶部,背景色透明,向上滑动时背景色变为白色
- 向上滑动时候当相应的内容即将没过头部时,对应的导航变为激活态
- 当滑到最底部时,最后的导航设为激活态,因为有的设计稿最后一块儿的区域高度是撑不开一屏的,所以最后一个永远不会高亮
- 点击导航滑到对应的区域上,实现两者的联动效果
代码实现
vue
<template>
<div
class="header"
:style="{
backgroundColor: scrollTop || navActive > 1 ? '#fff' : 'transparent'
}"
>
<div class="header-box">
<div class="logo-container">logo</div>
<div class="navBox">
<div
v-for="item in navList"
:key="item.value"
:class="['navItem', navActive == item.value ? 'active' : '']"
@click="goAnchor(item.id, item.value)"
>
{{ item.label }}
</div>
</div>
</div>
</div>
<div class="box-col" id="box">
<div id="part1">核心产品</div>
<div id="part2">应用场景</div>
<div id="part3">服务领域</div>
<div id="part4">联系我们</div>
</div>
</template>
js
<script>
export default {
name: 'Anchor',
data() {
return {
scrollTop: 0,
//锚点点击标识, 不然active就会来回抖动
navClickFlag: false,
// 滚动元素id
scrollWrap: 'box',
navActive: 1,
navList: [
{
label: '核心产品',
value: 1,
id: 'part1'
},
{
label: '应用场景',
value: 2,
id: 'part2'
},
{
label: '服务领域',
value: 3,
id: 'part3'
},
{
label: '联系我们',
value: 4,
id: 'part4'
}
],
// 各锚点的高度
offTopLs: []
}
},
mounted() {
// 监听滚动
window.addEventListener('scroll', this.handleScroll, true)
this.$nextTick(() => {
this.getoffTop()
})
},
beforeDestroy() {
// 销毁监听
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
// 计算锚点的高度
getoffTop() {
let offTop = []
for (let index = 0; index < this.navList.length; index++) {
const element = document.getElementById(this.navList[index].id)
if (element) offTop.push(element.offsetTop)
}
this.offTopLs = offTop
},
// 监听滚动
handleScroll(event) {
console.log(document.querySelector('.header').clientHeight)
const { scrollTop, scrollHeight, clientHeight } = event.target
const scrollWrap = document.getElementById(this.scrollWrap)
if (!this.navClickFlag && scrollWrap) {
let scTop = scrollWrap.scrollTop
this.scrollTop = scTop
for (let index = 0; index < this.offTopLs.length; index++) {
const element = this.offTopLs[index]
let offsetTop = scrollWrap.offsetTop + 88
if (scTop + offsetTop >= element) {
this.navActive = index + 1
}
if (scrollTop + clientHeight >= scrollHeight) {
// 滚动到底部,高亮最后一个
this.navActive = this.navList.length
}
}
}
},
// 锚点导航
goAnchor(keyId, val) {
if (this.navClickFlag || this.navActive === val) return
if (val === 1) this.scrollTop = 0
this.navClickFlag = true
this.navActive = val
const targetElement = document.getElementById(keyId)
if (targetElement) {
const offsetTop = targetElement.offsetTop
const scrollContainer = document.getElementById(this.scrollWrap)
if (scrollContainer) {
scrollContainer.scrollTo({
top: offsetTop - 88,
behavior: 'smooth'
})
}
}
setTimeout(() => {
this.navClickFlag = false
}, 1000)
}
}
}
</script
css
<style scoped lang="scss">
html {
overflow: hidden;
}
.box-col {
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
scroll-behavior: smooth;
}
.header {
width: 100vw;
background-color: #fff;
height: 88px;
position: fixed;
z-index: 10;
transition: background-color 0.3s;
&-box {
width: 1440px;
height: 100%;
display: flex;
margin: 0 auto;
justify-content: space-between;
align-items: center;
.logo-container {
width: 199px;
height: 42px;
font-size: 24px;
}
.navBox {
margin-left: 10px;
display: flex;
.navItem {
color: #000000;
padding: 8px 35px;
cursor: pointer;
&.active {
border-radius: 5px;
color: #fff;
background: rgb(0, 108, 238);
}
}
}
}
}
#part1,
#part2,
#part3,
#part4 {
height: 500px;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
}
#part1 {
background-color: #ccc;
}
#part2 {
background-color: #ccd;
}
#part3 {
background-color: #cad;
}
#part4 {
background-color: #cba;
}
</style>
遇到的问题及解决
一开始采用的是scrollIntoView,即:document.getElementById(keyId).scrollIntoView(true)
,但是这种方式有个弊端,不太灵活,会滚动到最顶部,导致有偏差,所以后面换成scrollTo的方式,灵活控制需要滚动的数值
结语
这里也是记录下,有需求的同学直接代码复制到项目中就能使用,避免了重复造轮子的缺陷,希望能帮助到大家。