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