tn-sticky.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <template>
  2. <view class="tn-sticky-class">
  3. <view
  4. class="tn-sticky__wrap"
  5. :class="[stickyClass]"
  6. :style="[stickyStyle]"
  7. >
  8. <view
  9. class="tn-sticky__item"
  10. :style="{
  11. position: fixed ? 'fixed' : 'static',
  12. top: stickyTop + 'px',
  13. left: left + 'px',
  14. width: width === 'auto' ? 'auto' : width + 'px',
  15. zIndex: elZIndex
  16. }"
  17. >
  18. <slot></slot>
  19. </view>
  20. </view>
  21. </view>
  22. </template>
  23. <script>
  24. export default {
  25. name: 'tn-sticky',
  26. props: {
  27. // 吸顶容器到顶部某个距离的时候进行吸顶
  28. // 在H5中,customNavBar的高度为45px
  29. offsetTop: {
  30. type: Number,
  31. default: 0
  32. },
  33. // H5顶部导航栏的高度
  34. h5NavHeight: {
  35. type: Number,
  36. default: 45
  37. },
  38. // 自定义顶部导航栏高度
  39. customNavHeight: {
  40. type: Number,
  41. default: 0
  42. },
  43. // 是否开启吸顶
  44. enabled: {
  45. type: Boolean,
  46. default: true
  47. },
  48. // 吸顶容器的背景颜色
  49. backgroundColor: {
  50. type: String,
  51. default: '#FFFFFF'
  52. },
  53. // z-index
  54. zIndex: {
  55. type: Number,
  56. default: 0
  57. },
  58. // 索引值,区分不同的吸顶组件
  59. index: {
  60. type: [String, Number],
  61. default: ''
  62. }
  63. },
  64. computed: {
  65. elZIndex() {
  66. return this.zIndex ? this.zIndex : this.$t.zIndex.sticky
  67. },
  68. backgroundColorStyle() {
  69. return this.$t.color.getBackgroundColorStyle(this.backgroundColor)
  70. },
  71. backgroundColorClass() {
  72. return this.$t.color.getBackgroundColorInternalClass(this.backgroundColor)
  73. },
  74. stickyClass() {
  75. let clazz = ''
  76. clazz += this.elClass
  77. if (this.backgroundColorClass) {
  78. clazz += ` ${this.backgroundColorClass}`
  79. }
  80. return clazz
  81. },
  82. stickyStyle() {
  83. let style = {}
  84. style.height = this.fixed ? this.height + 'px' : 'auto'
  85. if (this.backgroundColorStyle) {
  86. style.color = this.backgroundColorStyle
  87. }
  88. if (this.elZIndex) {
  89. style.zIndex = this.elZIndex
  90. }
  91. return style
  92. }
  93. },
  94. data() {
  95. return {
  96. // 监听组件别名
  97. stickyObserverName: 'tnStickyObserver',
  98. // 组件的唯一编号
  99. elClass: this.$t.uuid(),
  100. // 是否固定
  101. fixed: false,
  102. // 高度
  103. height: 'auto',
  104. // 宽度
  105. width: 'auto',
  106. // 距离顶部的距离
  107. stickyTop: 0,
  108. // 左边距离
  109. left: 0
  110. }
  111. },
  112. watch: {
  113. offsetTop(val) {
  114. this.initObserver()
  115. },
  116. enabled(val) {
  117. if (val === false) {
  118. this.fixed = false
  119. this.disconnectObserver(this.stickyObserverName)
  120. } else {
  121. this.initObserver()
  122. }
  123. },
  124. customNavHeight(val) {
  125. this.initObserver()
  126. }
  127. },
  128. mounted() {
  129. this.initObserver()
  130. },
  131. methods: {
  132. // 初始化监听组件的布局状态
  133. initObserver() {
  134. if (!this.enabled) return
  135. // #ifdef H5
  136. this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.h5NavHeight : this.h5NavHeight
  137. // #endif
  138. // #ifndef H5
  139. this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.customNavHeight : this.customNavHeight
  140. // #endif
  141. this.disconnectObserver(this.stickyObserverName)
  142. this._tGetRect('.' + this.elClass).then((res) => {
  143. this.height = res.height
  144. this.left = res.left
  145. this.width = res.width
  146. this.$nextTick(() => {
  147. this.connectObserver()
  148. })
  149. })
  150. },
  151. // 监听组件的布局状态
  152. connectObserver() {
  153. this.disconnectObserver(this.stickyObserverName)
  154. // 组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver
  155. const contentObserver = this.createIntersectionObserver({
  156. thresholds: [0.95, 0.98, 1]
  157. })
  158. contentObserver.relativeToViewport({
  159. top: -this.stickyTop
  160. })
  161. contentObserver.observe('.' + this.elClass, res => {
  162. if (!this.enabled) return
  163. this.setFixed(res.boundingClientRect.top)
  164. })
  165. this[this.stickyObserverName] = contentObserver
  166. },
  167. // 设置是否固定
  168. setFixed(top) {
  169. const fixed = top < this.stickyTop
  170. if (fixed) this.$emit('fixed', this.index)
  171. else if (this.fixed) this.$emit('unfixed', this.index)
  172. this.fixed = fixed
  173. },
  174. // 停止监听组件的布局状态
  175. disconnectObserver(observerName) {
  176. const observer = this[observerName]
  177. observer && observer.disconnect()
  178. }
  179. }
  180. }
  181. </script>
  182. <style>
  183. </style>