158 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
		
		
			
		
	
	
			158 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
|  | <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> |