long-waterflow-nested.uvue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <scroll-view ref="pageScrollView" class="page" :bounces="false" type="nested">
  3. <nested-scroll-header>
  4. <swiper ref="header" indicator-dots="true" circular="true">
  5. <swiper-item v-for="i in 3" :item-id="i">
  6. <image src="/static/shuijiao.jpg" style="width:100% ;height: 240px;"></image>
  7. <text style="position: absolute;">{{i}}</text>
  8. </swiper-item>
  9. </swiper>
  10. </nested-scroll-header>
  11. <nested-scroll-body style="height: 100%;">
  12. <view style="flex: 1;">
  13. <scroll-view ref="tabScroll" class="swiper-tabs" direction="horizontal" :show-scrollbar="false">
  14. <view class="flex-row" style="align-self: flex-start;">
  15. <text ref="swipertab" class="swiper-tabs-item" :class="swiperIndex==index ? 'swiper-tabs-item-active' : ''"
  16. v-for="(item, index) in swiperList" :key="index" @click="onTabClick(index)">
  17. {{item.name}}
  18. </text>
  19. </view>
  20. <view ref="indicator" class="swiper-tabs-indicator"></view>
  21. </scroll-view>
  22. <swiper ref="swiper" class="swiper-view" :current="swiperIndex" @transition="onSwiperTransition"
  23. @animationfinish="onSwiperAnimationfinish">
  24. <swiper-item class="swiper-item" v-for="(item, index) in swiperList" :key="index">
  25. <long-page ref="longPage" :id="item.id" :type="item.type" :preload="item.preload"></long-page>
  26. </swiper-item>
  27. </swiper>
  28. </view>
  29. </nested-scroll-body>
  30. </scroll-view>
  31. </template>
  32. <script>
  33. import longPage from './long-waterflow-page.uvue';
  34. type SwiperTabsItem = {
  35. x : number,
  36. w : number
  37. }
  38. type SwiperViewItem = {
  39. type : string,
  40. name : string,
  41. id : string,
  42. preload : boolean,
  43. }
  44. /**
  45. * 根据权重在两个值之间执行线性插值.
  46. * @constructor
  47. * @param {number} value1 - 第一个值,该值应为下限.
  48. * @param {number} value2 - 第二个值,该值应为上限.
  49. * @param {number} amount - 应介于 0 和 1 之间,指示内插的权重.
  50. * @returns {number} 内插值
  51. */
  52. function lerpNumber(value1 : number, value2 : number, amount : number) : number {
  53. return (value1 + (value2 - value1) * amount)
  54. }
  55. export default {
  56. components: {
  57. longPage
  58. },
  59. data() {
  60. return {
  61. swiperList: [
  62. {
  63. type: 'UpdatedDate',
  64. name: '最新上架',
  65. id: "list-id-1",
  66. preload: true
  67. } as SwiperViewItem,
  68. {
  69. type: 'FreeHot',
  70. name: '免费热榜',
  71. id: "list-id-2",
  72. preload: false
  73. } as SwiperViewItem,
  74. {
  75. type: 'PaymentHot',
  76. name: '付费热榜',
  77. id: "list-id-3",
  78. preload: false
  79. } as SwiperViewItem,
  80. {
  81. type: 'HotList',
  82. name: '热门总榜',
  83. id: "list-id-4",
  84. preload: false
  85. } as SwiperViewItem
  86. ] as SwiperViewItem[],
  87. swiperIndex: 0,
  88. pageScrollView: null as null | UniElement,
  89. headerHeight: 0,
  90. animationFinishIndex: 0,
  91. tabScrollView: null as null | UniElement,
  92. indicatorNode: null as null | UniElement,
  93. swiperWidth: 0,
  94. swiperTabsRect: [] as SwiperTabsItem[],
  95. nestedScrollChildId: ""
  96. }
  97. },
  98. onReady() {
  99. this.pageScrollView = this.$refs['pageScrollView'] as UniElement;
  100. this.headerHeight = (this.$refs['header'] as UniElement).offsetHeight
  101. this.swiperWidth = (this.$refs['swiper'] as UniElement).getBoundingClientRect().width
  102. this.tabScrollView = this.$refs['tabScroll'] as UniElement
  103. this.indicatorNode = this.$refs['indicator'] as UniElement
  104. this.cacheTabItemsSize()
  105. this.updateTabIndicator(this.swiperIndex, this.swiperIndex, 1)
  106. },
  107. onPullDownRefresh() {
  108. (this.$refs["longPage"]! as ComponentPublicInstance[])[this.swiperIndex].$callMethod('refreshData', () => {
  109. uni.stopPullDownRefresh()
  110. })
  111. },
  112. methods: {
  113. onTabClick(index : number) {
  114. this.setSwiperIndex(index, false)
  115. },
  116. onSwiperTransition(e : SwiperTransitionEvent) {
  117. // 微信 skyline 每项完成触发 Animationfinish,偏移值重置
  118. // 微信 webview 全部完成触发 Animationfinish,偏移值累加
  119. // 在滑动到下一个项的过程中,再次反向滑动,偏移值递减
  120. // uni-app-x 和微信 webview 行为一致
  121. const offset_x = e.detail.dx
  122. // 计算当前索引并重置差异
  123. const current_offset_x = offset_x % this.swiperWidth
  124. const current_offset_i = offset_x / this.swiperWidth
  125. const current_index = this.animationFinishIndex + parseInt(current_offset_i + '')
  126. // 计算目标索引及边界检查
  127. let move_to_index = current_index
  128. if (current_offset_x > 0 && move_to_index < this.swiperList.length - 1) {
  129. move_to_index += 1
  130. } else if (current_offset_x < 0 && move_to_index > 0) {
  131. move_to_index -= 1
  132. }
  133. // 计算偏移百分比
  134. const percentage = Math.abs(current_offset_x) / this.swiperWidth
  135. // 通知更新指示线
  136. if (current_index != move_to_index) {
  137. this.updateTabIndicator(current_index, move_to_index, percentage)
  138. }
  139. // 首次可见时初始化数据
  140. this.initSwiperItemData(move_to_index)
  141. },
  142. onSwiperAnimationfinish(e : SwiperAnimationFinishEvent) {
  143. this.setSwiperIndex(e.detail.current, true)
  144. this.animationFinishIndex = e.detail.current
  145. },
  146. cacheTabItemsSize() {
  147. this.swiperTabsRect.length = 0
  148. const tabs = this.$refs["swipertab"] as UniElement[]
  149. tabs.forEach((node) => {
  150. this.swiperTabsRect.push({
  151. x: node.offsetLeft,
  152. w: node.offsetWidth
  153. } as SwiperTabsItem)
  154. })
  155. },
  156. setSwiperIndex(index : number, updateIndicator : boolean) {
  157. if (this.swiperIndex === index) {
  158. return
  159. }
  160. this.swiperIndex = index
  161. this.initSwiperItemData(index)
  162. if (updateIndicator) {
  163. this.updateTabIndicator(index, index, 1)
  164. }
  165. },
  166. updateTabIndicator(current_index : number, move_to_index : number, percentage : number) {
  167. const current_size = this.swiperTabsRect[current_index]
  168. const move_to_size = this.swiperTabsRect[move_to_index]
  169. // 计算指示线 左边距 和 宽度 在移动过程中的线性值
  170. const indicator_line_x = lerpNumber(current_size.x, move_to_size.x, percentage)
  171. const indicator_line_w = lerpNumber(current_size.w, move_to_size.w, percentage)
  172. // 更新指示线
  173. // #ifdef APP
  174. const x = indicator_line_x + indicator_line_w / 2
  175. this.indicatorNode?.style?.setProperty('transform', `translateX(${x}px) scaleX(${indicator_line_w})`)
  176. // #endif
  177. // #ifdef WEB
  178. // TODO chrome windows系统 transform scaleX渲染bug
  179. const x = indicator_line_x
  180. this.indicatorNode?.style?.setProperty('width', `${indicator_line_w}px`)
  181. this.indicatorNode?.style?.setProperty('transform', `translateX(${x}px)`)
  182. // #endif
  183. // 滚动到水平中心位置
  184. const scroll_x = x - this.swiperWidth / 2
  185. if (this.tabScrollView !== null) {
  186. this.tabScrollView!.scrollLeft = scroll_x
  187. }
  188. },
  189. initSwiperItemData(index : number) {
  190. if (!this.swiperList[index].preload) {
  191. this.swiperList[index].preload = true;
  192. (this.$refs["longPage"]! as ComponentPublicInstance[])[index].$callMethod('loadData', null)
  193. }
  194. //swiper页面切换需要重新赋值嵌套滚动子元素的id
  195. this.nestedScrollChildId = this.swiperList[index].id
  196. }
  197. }
  198. }
  199. </script>
  200. <style>
  201. .flex-row {
  202. flex-direction: row;
  203. }
  204. .page {
  205. flex: 1;
  206. }
  207. .search-bar {
  208. padding: 10px;
  209. }
  210. .swiper-list {
  211. height: 100%;
  212. /* #ifdef WEB */
  213. flex: 1;
  214. /* #endif */
  215. }
  216. .swiper-tabs {
  217. background-color: #ffffff;
  218. flex-direction: column;
  219. }
  220. .swiper-tabs-item {
  221. color: #555;
  222. font-size: 16px;
  223. padding: 12px 25px;
  224. white-space: nowrap;
  225. }
  226. .swiper-tabs-item-active {
  227. color: #007AFF;
  228. }
  229. .swiper-tabs-indicator {
  230. width: 1px;
  231. height: 2px;
  232. background-color: #007AFF;
  233. }
  234. .swiper-view {
  235. flex: 1;
  236. }
  237. .swiper-item {
  238. flex: 1;
  239. }
  240. </style>