在我们日常开发业务中,时常会遇到一些滚动吸顶的交互; css有提供position:sticky属性,但是该属性使用限制多,并且最大的问题,就是在安卓机上的表现简直惨不忍睹。 鉴于sticky其实就是fixed与relative的结合(当修饰的目标节点再屏幕中时表现为relative,当要超出的时候是fixed的形式展现),所以我们完全可以使用js来实现这一功能。
<template lang="pug">
.sticky-wrap(:style="compsRootStyle")
.sticky-change-wrap(:class="className" :style="stickyStyle")
slot
| this sticky content
</template>
sticky-change-wrap,根据滚动情况,做fixed或者非fixed变化,fixed中会脱离文档流,造成后续元素陡然上移,或者非fixed时,下面内容陡然被挤下去。sticky-wrap盒子外层,需设置高,就是解决上述变化抖动的问题;
export default {
name: 'Sticky',
props: {
stickyTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 1
},
className: {
type: String,
default: ''
}
},
data() {
return {
position: '',
width: '',
isSticky: false,
compsRootStyle: {}
}
},
computed: {
stickyStyle() {
const { position, width, stickyTop, isSticky } = this
return {
width
position,
top: isSticky ? `${stickyTop}px` : '',
...this.compsRootStyle
}
}
},
mounted() {
this.compsRootStyle = { height: this.$el.getBoundingClientRect().height, zIndex: this.zIndex }
window.addEventListener('scroll', this.handleScroll)
this.$once('hook:beforeDestroy', () => {
window.removeEventListener('scroll', this.handleScroll)
})
},
methods: {
handleScroll() {
const width = this.$el.getBoundingClientRect().width
this.width = width || 'auto'
const offsetTop = this.$el.getBoundingClientRect().top
offsetTop < this.stickyTop ? this.setSticky() : this.setNotSticky()
},
setSticky() {
if (this.isSticky) return
this.isSticky = true
this.position = 'fixed'
this.width = this.width + 'px'
},
setNotSticky() {
if (!this.isSticky) return
this.isSticky = false
this.position = ''
this.width = 'auto'
}
}
}
进一步完善
上述代码已经很好的实现了滚动到目标top时吸附,滚出该目标时随内容滚动的效果; 但是在pc端有一个bug,就是在fixed定位之后,其宽度将不再和父元素的保持一致。这时会出现,改变屏幕大小,宽度并不发变化的问题;解决方案: 加一个resize监听
export default {
mounted() {
window.addEventListener('resize', this.handleResize)
this.$once('hook:beforeDestroy', () => {
window.removeEventListener('resize', this.handleResize)
})
},
methods: {
handleResize() {
if (this.isSticky) {
this.width = this.$el.getBoundingClientRect().width + 'px'
}
},
}
}
</script>
在组件keep-alive中,首次进入会触发mounted,但是切换其他组件后再返回不会再执行mounted等生命周期函数,这是因为组件已经被缓存了。
唯一会执行的是activated。
使用
sticky(:sticky-top="0")
.item(v-for="item in 100") placeholder