| <template> |
| <view class=""> |
| <view class="u-sticky-wrap" :class="[elClass]" :style="{ |
| height: fixed ? height + 'px' : 'auto', |
| backgroundColor: bgColor |
| }"> |
| <view class="u-sticky" :style="{ |
| position: fixed ? 'fixed' : 'static', |
| top: stickyTop + 'px', |
| left: left + 'px', |
| width: width == 'auto' ? 'auto' : width + 'px', |
| zIndex: uZIndex |
| }"> |
| <slot></slot> |
| </view> |
| </view> |
| </view> |
| </template> |
| |
| <script> |
| /** |
| * sticky 吸顶 |
| * @description 该组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。 |
| * @tutorial https://www.uviewui.com/components/sticky.html |
| * @property {String Number} offset-top 吸顶时与顶部的距离,单位rpx(默认0) |
| * @property {String Number} index 自定义标识,用于区分是哪一个组件 |
| * @property {Boolean} enable 是否开启吸顶功能(默认true) |
| * @property {String} bg-color 组件背景颜色(默认#ffffff) |
| * @property {String Number} z-index 吸顶时的z-index值(默认970) |
| * @property {String Number} h5-nav-height 导航栏高度,自定义导航栏时(无导航栏时需设置为0),需要传入此值,单位px(默认44) |
| * @event {Function} fixed 组件吸顶时触发 |
| * @event {Function} unfixed 组件取消吸顶时触发 |
| * @example <u-sticky offset-top="200"><view>塞下秋来风景异,衡阳雁去无留意</view></u-sticky> |
| */ |
| export default { |
| name: "u-sticky", |
| props: { |
| // 吸顶容器到顶部某个距离的时候,进行吸顶,在H5平台,NavigationBar为44px |
| offsetTop: { |
| type: [Number, String], |
| default: 0 |
| }, |
| //列表中的索引值 |
| index: { |
| type: [Number, String], |
| default: '' |
| }, |
| // 是否开启吸顶功能 |
| enable: { |
| type: Boolean, |
| default: true |
| }, |
| // h5顶部导航栏的高度 |
| h5NavHeight: { |
| type: [Number, String], |
| default: 44 |
| }, |
| // 吸顶区域的背景颜色 |
| bgColor: { |
| type: String, |
| default: '#ffffff' |
| }, |
| // z-index值 |
| zIndex: { |
| type: [Number, String], |
| default: '' |
| } |
| }, |
| data() { |
| return { |
| fixed: false, |
| height: 'auto', |
| stickyTop: 0, |
| elClass: this.$u.guid(), |
| left: 0, |
| width: 'auto', |
| }; |
| }, |
| watch: { |
| offsetTop(val) { |
| this.initObserver(); |
| }, |
| enable(val) { |
| if (val == false) { |
| this.fixed = false; |
| this.disconnectObserver('contentObserver'); |
| } else { |
| this.initObserver(); |
| } |
| } |
| }, |
| computed: { |
| uZIndex() { |
| return this.zIndex ? this.zIndex : this.$u.zIndex.sticky; |
| } |
| }, |
| mounted() { |
| this.initObserver(); |
| }, |
| methods: { |
| initObserver() { |
| if (!this.enable) return; |
| // #ifdef H5 |
| this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.h5NavHeight : this.h5NavHeight; |
| // #endif |
| // #ifndef H5 |
| this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) : 0; |
| // #endif |
| |
| this.disconnectObserver('contentObserver'); |
| this.$uGetRect('.' + this.elClass).then((res) => { |
| this.height = res.height; |
| this.left = res.left; |
| this.width = res.width; |
| this.$nextTick(() => { |
| this.observeContent(); |
| }); |
| }); |
| }, |
| observeContent() { |
| this.disconnectObserver('contentObserver'); |
| const contentObserver = this.createIntersectionObserver({ |
| thresholds: [0.95, 0.98, 1] |
| }); |
| contentObserver.relativeToViewport({ |
| top: -this.stickyTop |
| }); |
| contentObserver.observe('.' + this.elClass, res => { |
| if (!this.enable) return; |
| this.setFixed(res.boundingClientRect.top); |
| }); |
| this.contentObserver = contentObserver; |
| }, |
| setFixed(top) { |
| const fixed = top < this.stickyTop; |
| if (fixed) this.$emit('fixed', this.index); |
| else if(this.fixed) this.$emit('unfixed', this.index); |
| this.fixed = fixed; |
| }, |
| disconnectObserver(observerName) { |
| const observer = this[observerName]; |
| observer && observer.disconnect(); |
| }, |
| }, |
| beforeDestroy() { |
| this.disconnectObserver('contentObserver'); |
| } |
| }; |
| </script> |
| |
| <style scoped lang="scss"> |
| @import "../../libs/css/style.components.scss"; |
| |
| .u-sticky { |
| z-index: 9999999999; |
| } |
| </style> |