index.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/classNames'
  3. import styleToCssString from '../helpers/styleToCssString'
  4. baseComponent({
  5. relations: {
  6. '../index-item/index': {
  7. type: 'child',
  8. observer() {
  9. this.debounce(this.updated)
  10. },
  11. },
  12. },
  13. properties: {
  14. prefixCls: {
  15. type: String,
  16. value: 'wux-index',
  17. },
  18. height: {
  19. type: [String, Number],
  20. value: 300,
  21. observer: 'updateStyle',
  22. },
  23. showIndicator: {
  24. type: Boolean,
  25. value: true,
  26. },
  27. },
  28. data: {
  29. scrollTop: 0,
  30. sections: [],
  31. moving: false,
  32. current: 0,
  33. currentName: '',
  34. extStyle: '',
  35. },
  36. computed: {
  37. classes() {
  38. const { prefixCls } = this.data
  39. const wrap = classNames(prefixCls)
  40. const nav = `${prefixCls}__nav`
  41. const navItem = `${prefixCls}__nav-item`
  42. const indicator = `${prefixCls}__indicator`
  43. return {
  44. wrap,
  45. nav,
  46. navItem,
  47. indicator,
  48. }
  49. },
  50. },
  51. methods: {
  52. /**
  53. * 更新样式
  54. */
  55. updateStyle(height = this.data.height) {
  56. const extStyle = styleToCssString({ height })
  57. if (extStyle !== this.data.extStyle) {
  58. this.setData({
  59. extStyle,
  60. })
  61. }
  62. },
  63. /**
  64. * 更新元素
  65. */
  66. updated() {
  67. const elements = this.getRelationNodes('../index-item/index')
  68. if (elements.length > 0) {
  69. elements.forEach((element, index) => {
  70. element.updated(index)
  71. })
  72. this.getNavPoints()
  73. }
  74. if (this.data.sections.length !== elements.length) {
  75. this.setData({
  76. sections: elements.map((element) => element.data)
  77. })
  78. }
  79. },
  80. /**
  81. * 设置当前激活的元素
  82. */
  83. setActive(current, currentName) {
  84. if (current !== this.data.current || currentName !== this.data.currentName) {
  85. const target = this.data.sections.filter((section) => section.index === current && section.name === currentName)[0]
  86. if (target) {
  87. this.setData({
  88. current,
  89. currentName,
  90. scrollTop: target.top,
  91. })
  92. }
  93. }
  94. this.triggerEvent('change', { index: current, name: currentName })
  95. },
  96. /**
  97. * 手指触摸动作开始
  98. */
  99. onTouchStart(e) {
  100. if (this.data.moving) return
  101. const { index, name } = e.target.dataset
  102. this.setActive(index, name)
  103. this.setData({ moving: true })
  104. },
  105. /**
  106. * 手指触摸后移动
  107. */
  108. onTouchMove(e) {
  109. const target = this.getTargetFromPoint(e.changedTouches[0].pageY)
  110. if (target !== undefined) {
  111. const { index, name } = target.dataset
  112. this.setActive(index, name)
  113. }
  114. },
  115. /**
  116. * 手指触摸动作结束
  117. */
  118. onTouchEnd(e) {
  119. if (!this.data.moving) return
  120. setTimeout(() => this.setData({ moving: false }), 300)
  121. },
  122. /**
  123. * 滚动事件的回调函数
  124. */
  125. onScroll(e) {
  126. if (this.data.moving) return
  127. const { scrollTop } = e.detail
  128. this.data.sections.forEach((section, index) => {
  129. if (scrollTop < section.top + section.height && scrollTop >= section.top) {
  130. if (index !== this.data.current || section.name !== this.data.currentName) {
  131. this.setData({
  132. current: index,
  133. currentName: section.name,
  134. })
  135. }
  136. }
  137. })
  138. },
  139. /**
  140. * 获取右侧导航对应的坐标
  141. */
  142. getNavPoints() {
  143. const className = `.${this.data.prefixCls}__nav-item`
  144. wx
  145. .createSelectorQuery()
  146. .in(this)
  147. .selectAll(className)
  148. .boundingClientRect((rects) => {
  149. if (rects.filter((n) => !n).length) return
  150. this.setData({
  151. points: rects.map((n) => ({ ...n, offsets: [n.top, n.top + n.height] })),
  152. })
  153. })
  154. .exec()
  155. },
  156. /**
  157. * 根据坐标获得对应的元素
  158. */
  159. getTargetFromPoint(y) {
  160. const { points } = this.data
  161. let target
  162. for (let i = points.length - 1; i >= 0; i--) {
  163. const [a, b] = points[i].offsets
  164. // 1.判断是否为第一个元素且大于最大坐标点
  165. // 2.判断是否为最后一个元素且小于最小坐标点
  166. // 3.判断是否包含于某个坐标系内
  167. if ((i === points.length - 1 && y > b) || (i === 0 && y < a) || (y >= a && y <= b)) {
  168. target = points[i]
  169. break
  170. }
  171. }
  172. return target
  173. },
  174. },
  175. ready() {
  176. this.updateStyle()
  177. this.getNavPoints()
  178. },
  179. })