WindowAffix 窗口图钉
大约 2 分钟
概述
WindowAffix 能将页面元素固定在一个区域内。
原理
想要实现 Affix 效果,需要获取元素的位置。我们可以借助 Element.getBoundingClientRect()
,该方法返回一个 DOMRect
对象,通过这个对象可以获取元素上、右、下、左相对于视窗左上边缘的距离、xy距离以及元素宽高。
详情参见 MDN 文档:Element.getBoundingClientRect()
若使用 top
定位。向下滚动时,元素随着页面不断向上运动,当元素顶部到达 offset,元素会停止移动并固定在该处。由此可见,触发的条件为 top < offset
,且触发后元素的定位将变为 fixed
,其中, top
为元素顶部相对于视窗上边缘的距离。
若使用 bottom
定位。与 top
的情况相反,页面向下运动,元素底部到达 offset 将会固定。触发的条件为 bottom > clientHeight - offset
,其中,bottom
为元素底部相对于视窗上边缘的距离, clientHeight
为视窗的高度。
实现
- 计算属性
affixStyle
动态更新组件的样式 - 除了页面上下滚动,视窗的大小变化同样会触发 Affix 效果
<template>
<div ref="root" class="window-affix">
<div :class="{ 'window-affix__fixed': state.fixed }" :style="affixStyle">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
offset: {
type: Number,
default: 0
},
position: {
type: String,
default: 'top'
},
zIndex: {
type: Number,
default: 100
}
},
data() {
return {
state: { // 图钉状态
fixed: false,
width: 0,
height: 0,
left: 0,
scrollTop: 0,
clientHeight: 0
}
}
},
watch: {
'state.fixed'(newVal) { // 向父组件触发 change 事件
this.$emit('changr', this.state.fixed)
}
},
mounted() {
window.addEventListener('scroll', this.onScroll)
window.addEventListener('resize', this.updateState)
},
beforeDestroy() {
window.removeEventListener('scroll', this.onScroll)
window.removeEventListener('resize', this.updateState)
},
computed: {
affixStyle() { // 图钉样式
if (!this.state.fixed) {
return
}
const offset = this.offset ? `${this.offset}px` : 0
const left = `${this.state.left}px`
return {
height: `${this.state.height}px`,
width: `${this.state.width}px`,
top: this.position === 'top' ? offset : '',
left: left,
bottom: this.position === 'bottom' ? offset : '',
zIndex: this.zIndex,
}
}
},
methods: {
onScroll() { // 滚动回调函数
// 更新图钉状态
this.updateState()
// 向父组件触发 scroll 事件
this.$emit('scroll', {
scrollTop: this.state.scrollTop,
fixed: this.state.fixed
})
},
updateState() { // 图钉状态更新函数
const rootRect = this.$refs.root.getBoundingClientRect()
this.state.width = rootRect.width
this.state.height = rootRect.height
this.state.left = rootRect.left
this.state.scrollTop = document.documentElement.scrollTop
this.state.clientHeight = document.documentElement.clientHeight
if (this.position === 'top') { // top 定位
this.state.fixed = this.offset > rootRect.top
} else { // bottom 定位
this.state.fixed = rootRect.bottom > this.state.clientHeight - this.offset
}
}
}
}
</script>