Files
hoc-element-affix/src/components/ScrollAffix/index.vue

137 lines
3.2 KiB
Vue
Raw Normal View History

2020-04-03 16:34:17 +08:00
<template>
2020-04-04 20:34:09 +08:00
<div>
<div
v-if="showPlaceHolder"
:style="stylePlaceHolder"
2020-10-06 17:03:32 +08:00
/>
2020-04-04 20:34:09 +08:00
<div
ref="scroll-affix"
:style="affixStyle"
class="scroll-affix-container"
>
<slot
v-bind="{
affixed: getAffixed
}"
/>
2020-04-04 20:34:09 +08:00
</div>
2020-04-03 16:34:17 +08:00
</div>
</template>
<script>
export default {
name: 'Affix',
props: {
offsetTop: {
type: Number,
default: 0
}
},
data () {
return {
affixStyle: {
2020-04-04 21:11:18 +08:00
position: 'initial',
2020-04-03 16:34:17 +08:00
top: 'initial'
},
2020-04-04 20:34:09 +08:00
2020-04-05 16:44:20 +08:00
// 占位区域样式
stylePlaceHolder: {},
// 占位区域
2020-04-04 20:34:09 +08:00
showPlaceHolder: false,
2020-04-05 16:44:20 +08:00
// 实例
2020-04-03 16:34:17 +08:00
instance: '',
2020-04-05 16:44:20 +08:00
// 用于记录实例的初始状态下的位置
2020-04-03 16:34:17 +08:00
defaultInstancePosition: ''
}
},
computed: {
getAffixed () {
return this.affixStyle.position === 'fixed'
}
},
2020-04-03 16:34:17 +08:00
mounted () {
this.$nextTick(() => {
this.instance = this.$refs['scroll-affix']
this.createAffix()
})
},
2020-10-06 17:03:32 +08:00
beforeDestroy () {
document.removeEventListener('scroll', this.scrollListener)
},
2020-04-03 16:34:17 +08:00
methods: {
getInstanceRect () {
return this.instance.getBoundingClientRect()
},
2020-04-05 16:44:20 +08:00
2020-04-03 16:34:17 +08:00
getWindowScrollTop () {
return window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop
},
2020-04-05 16:44:20 +08:00
2020-04-03 16:34:17 +08:00
createAffix () {
this.defaultInstancePosition = this.getInstanceRect().top
this.beforeListener()
document.addEventListener('scroll', this.scrollListener)
},
2020-04-05 16:44:20 +08:00
2020-04-03 16:34:17 +08:00
setFixedForInstance () {
this.affixStyle = {
position: 'fixed',
top: `${this.offsetTop}px`
}
this.$emit('change', true)
2020-04-03 16:34:17 +08:00
},
2020-04-05 16:44:20 +08:00
// 用于设置实例在固定后的空白占位
2020-04-04 20:34:09 +08:00
setPlaceHolder () {
2020-04-04 20:36:09 +08:00
this.showPlaceHolder = true
2020-04-04 20:34:09 +08:00
const instanceRect = this.getInstanceRect()
this.stylePlaceHolder = {
2020-04-04 21:04:30 +08:00
width: `${instanceRect.width}px`,
height: `${instanceRect.height}px`
2020-04-04 20:34:09 +08:00
}
},
2020-04-03 16:34:17 +08:00
beforeListener () {
2020-04-05 16:44:20 +08:00
// 若下一次进入页面发现滚动条所处位置已经超过了实例,则立即固定
2020-04-03 16:34:17 +08:00
if (this.defaultInstancePosition < this.offsetTop) {
this.setFixedForInstance()
2020-04-04 20:34:09 +08:00
this.setPlaceHolder()
2020-04-03 16:34:17 +08:00
}
2020-04-04 20:34:09 +08:00
2020-04-03 16:34:17 +08:00
this.defaultInstancePosition = this.getWindowScrollTop() + this.defaultInstancePosition
},
scrollListener () {
const offsetTop = this.getInstanceRect().top
2020-04-05 16:44:20 +08:00
// 当实例距离顶部的距离刚好接近(0px+设置的 top 距离)时,则立即固定
if (offsetTop < this.offsetTop) {
2020-04-03 16:34:17 +08:00
this.setFixedForInstance()
2020-04-04 20:34:09 +08:00
this.setPlaceHolder()
2020-04-03 16:34:17 +08:00
}
2020-04-04 20:34:09 +08:00
const windowScrollTop = this.getWindowScrollTop()
2020-04-03 16:34:17 +08:00
const isArrivalDefault = (this.defaultInstancePosition - this.offsetTop) >= windowScrollTop
2020-04-04 20:34:09 +08:00
2020-04-05 16:44:20 +08:00
// 当实例的初始位置减去设置的 top 距离刚好接近滚动条滚过的距离时(即一直在向上滚动)&& 实例已经在固定状态,则取消固定
2020-04-03 16:34:17 +08:00
if (isArrivalDefault && this.affixStyle.position === 'fixed') {
this.affixStyle = {}
2020-04-04 20:36:09 +08:00
this.showPlaceHolder = false
2020-04-04 20:34:09 +08:00
this.stylePlaceHolder = {}
this.$emit('change', false)
2020-04-03 16:34:17 +08:00
}
}
}
}
</script>
<style lang="scss" scoped>
.scroll-affix-container {
position: initial;
top: initial;
}
</style>