vue锚点双向绑定
- 需求描述:
头部固定吸顶,右侧菜单,左侧长页面,要求左侧滚动右侧菜单对应高亮,点击右侧菜单,左侧页面滚动到对应位置。(这个对应位置就是模块头部刚好在固定头部下面)
- 需求分析:
头部和右侧可使用fixed定位,左侧长页面不限制高度。有两件事需要处理:监听滚动事件,实现滚动效果。
- 解决问题
// 监听事件添加和销毁
mounted() {
document.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
document.removeEventListener('scroll', this.handleScroll)
}
// 滚动事件
handleScroll() {
// 滚动高度
const sTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
// 最大值,当页面滚动到最大值后,下面的模块不会再向上滚动了
const max = this.sArea[8] + 10
// 滚动停止后执行函数
runLastFn(_ => {
this.initSArea()
if (sTop < max) {
// 判读页面滚动对应的菜单index
const idx = this.findAreaIndex(sTop)
if (!this.mFlag) return
this.currentFloor = 'block' + idx
}
})
},
// 初始化不同模块(12个)滚动间距,注意每个模块都设置一个ref,每个模块滚动的距离组成一个数组,与菜单对应
initSArea() {
const arr = []
for (let i = 1; i < 12; i++) {
const height = this.$refs[`block${i}`].offsetTop - 240 // 240 是所有模块滚动目标位置
arr.push(height > 0 ? height : 0)
}
this.sArea = arr
this.boxScreenHeight = this.$refs.boxRef.scrollHeight
},
// 滚动距离在数组中位置,以此判读哪个菜单应该在选择状态
findAreaIndex(val) {
const sArea = this.sArea
const len = sArea.length
if (val < sArea[1]) return 1
if (val > sArea[len - 1]) return len - 1
let idx = 0
let i = 0
for (i; i < len; i++) {
if (sArea[i] > val) {
idx = i
break
}
}
return idx
}
// 点击菜单滚动到对应模块
gotoBlock(el, speed) {
const idx = Number(el.slice(5))
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop // 滚动条距离顶部高度
let currentTop = scrollTop // 默认滚动位置为当前滚动条位置,若改为0,则每次都会从顶部滚动到指定位置
let top = 0 // 需要滚动到的位置
if (idx < 9) {
this.mFlag = true
const i = idx - 1 > 0 ? idx - 1 : 0 // 有个回到顶部菜单,所以第一个和第二个滚动位置一样
top = this.sArea[i] + 1 // 模块需要滚动的位置
} else {
this.mFlag = false // 页面无需滚动了,但是菜单被点击后仍要选中状态
top = this.boxScreenHeight
this.currentFloor = el
setTimeout(_ => {
this.mFlag = true
}, 1200)
}
let requestId
function step() {
// 如果需要滚动的位置和当前位置之差小于步长speed,直接滚动到目标位置,否则按照步长滚动
if (Math.abs(currentTop - top) < speed) {
window.scrollTo(0, top)
} else {
if (scrollTop < top) {
// 如果滚动条的高度小于元素距离顶部的高度
if (currentTop <= top) {
window.scrollTo(0, currentTop)
requestId = window.requestAnimationFrame(step)
} else {
window.cancelAnimationFrame(requestId)
}
// 向下滚动
currentTop += speed
} else {
if (top <= currentTop) {
window.scrollTo(0, currentTop - speed)
requestId = window.requestAnimationFrame(step)
} else {
window.cancelAnimationFrame(requestId)
}
// 向上滚动
currentTop -= speed
}
}
}
window.requestAnimationFrame(step)
},