scs-scroll-navbar.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <!--
  2. * @Author: jmy
  3. * @Date: 2025-12-13 12:02:41
  4. * @LastEditors: Please set LastEditors
  5. * @LastEditTime: 2025-12-19 15:02:01
  6. * @Description:
  7. -->
  8. <template>
  9. <scroll-view class="scs-scroll-navbar" scroll-x :scroll-left="scrollLeft" scroll-with-animation="true">
  10. <view v-for="(item, index) in tabsData" :key="index" class="nav-item"
  11. :class="index === tabCurrentIndex ? 'act-current' : ''" @tap="tabChange(index)" :id="'item-' + index">
  12. <view class="item-title" :style="{ 'color': index === tabCurrentIndex ? activeColor : textColor }">
  13. {{ item[nameKey] }}
  14. </view>
  15. </view>
  16. <view class="nav-underline" :style="underlineStyle">
  17. <image v-if="isUseActImg" :src="activeImage"></image>
  18. </view>
  19. </scroll-view>
  20. </template>
  21. <script>
  22. export default {
  23. name: 'scs-scroll-navbar',
  24. props: {
  25. // 导航项数据
  26. tabsData: {
  27. type: Array,
  28. default() {
  29. return [];
  30. },
  31. },
  32. // 当前选中的导航项索引
  33. tabCurrentIndex: {
  34. type: Number,
  35. default: 0,
  36. },
  37. // 导航项名称键名
  38. nameKey: {
  39. type: String,
  40. default: 'name',
  41. },
  42. // 导航项左侧间距px
  43. calcLeft: {
  44. type: Number,
  45. default: 12,
  46. },
  47. // 是否需要滚动
  48. isScroll: {
  49. type: Boolean,
  50. default: true,
  51. },
  52. // 选中导航项的字体颜色
  53. activeColor: {
  54. type: String,
  55. default: '#333333 ',
  56. },
  57. // 未选中导航项的字体颜色
  58. textColor: {
  59. type: String,
  60. default: '#4D4D4D',
  61. },
  62. // 选中导航项的图片
  63. activeImage: {
  64. type: String,
  65. default: '/static/images/home/tj_tab_hover_icon20@2x.png',
  66. },
  67. // 是否使用选中导航项的图片
  68. isUseActImg: {
  69. type: Boolean,
  70. default: false,
  71. },
  72. },
  73. data() {
  74. return {
  75. scrollLeft: 0,
  76. widthList: [],
  77. screenWidth: 0,
  78. itemPositions: [], // 存储每个导航项的位置信息
  79. };
  80. },
  81. computed: {
  82. // 计算下划线的样式
  83. underlineStyle() {
  84. if (this.itemPositions.length === 0 || this.tabCurrentIndex >= this.itemPositions.length) {
  85. return {};
  86. }
  87. const position = this.itemPositions[this.tabCurrentIndex];
  88. if (this.tabCurrentIndex === 0) {
  89. position.left = 1
  90. }
  91. return `width:${position.width}px;left:${position.left}px;opacity:1`;
  92. },
  93. },
  94. watch: {
  95. tabCurrentIndex: function (val) {
  96. if (!this.isScroll) return
  97. this.tabScroll(val);
  98. },
  99. tabsData: function (val) {
  100. this.init();
  101. },
  102. },
  103. methods: {
  104. // 导航栏点击
  105. tabChange(index) {
  106. let self = this;
  107. self.$emit('tabChange', index);
  108. },
  109. // 导航栏滚动
  110. tabScroll(index) {
  111. var widthArr = this.widthList;
  112. var scrollWidth = 0;
  113. for (var i = 0; i < index + 1; i++) {
  114. scrollWidth += widthArr[i];
  115. }
  116. let currentWidth = widthArr[index];
  117. // scrollView 居中算法
  118. // 减去固定宽度位移
  119. // 减去选中的bar的宽度的一半
  120. scrollWidth -= this.screenWidth / 2;
  121. scrollWidth -= currentWidth / 2;
  122. this.scrollLeft = scrollWidth;
  123. },
  124. //滚动左侧
  125. tabScrollLeft() {
  126. this.scrollLeft = 0.5;
  127. setTimeout(() => {
  128. this.scrollLeft = 0;
  129. }, 500);
  130. },
  131. // 计算导航项宽度
  132. calculateItemWidth() {
  133. let widthArr = [];
  134. let positions = [];
  135. Promise.all(
  136. this.tabsData.map((_, index) =>
  137. new Promise((resolve) => {
  138. uni
  139. .createSelectorQuery()
  140. .in(this)
  141. .select(`#item-${index}`)
  142. .boundingClientRect((data) => {
  143. if (data) {
  144. widthArr.push(data.width);
  145. positions.push({ left: data.left - this.calcLeft, width: data.width });
  146. } else {
  147. // 兜底默认宽度
  148. widthArr.push(100);
  149. positions.push({ left: index * 100, width: 100 });
  150. }
  151. resolve();
  152. })
  153. .exec();
  154. })
  155. )
  156. ).then(() => {
  157. // 所有节点查询完毕,一次性赋值
  158. this.widthList = widthArr;
  159. this.itemPositions = positions;
  160. });
  161. },
  162. // 计算窗口宽度
  163. calculateWindowWidth() {
  164. var info = uni.getSystemInfoSync();
  165. this.screenWidth = info.screenWidth;
  166. },
  167. init() {
  168. const that = this;
  169. this.calculateWindowWidth();
  170. setTimeout(function () {
  171. that.calculateItemWidth();
  172. }, 1000);
  173. },
  174. },
  175. created() {
  176. this.init();
  177. },
  178. };
  179. </script>
  180. <style lang="scss" scoped>
  181. ::v-deep.uni-scroll-view::-webkit-scrollbar {
  182. display: none;
  183. }
  184. .scs-scroll-navbar {
  185. width: 100%;
  186. height: 90rpx;
  187. white-space: nowrap;
  188. .nav-item {
  189. height: 100%;
  190. text-align: center;
  191. display: inline-block;
  192. position: relative;
  193. font-size: 40rpx;
  194. margin-right: 32rpx;
  195. &:last-child {
  196. margin-right: 0;
  197. }
  198. .item-title {
  199. line-height: 90rpx;
  200. display: inline-block;
  201. position: relative;
  202. z-index: 10;
  203. }
  204. }
  205. .act-current {
  206. font-size: 40rpx;
  207. font-weight: bold;
  208. }
  209. .nav-underline {
  210. position: relative;
  211. height: 16rpx;
  212. // background: linear-gradient( 90deg, rgba(56,217,125,0.5) 0%, rgba(56,217,125,0) 100%);
  213. border-radius: 8rpx;
  214. bottom: 15rpx;
  215. left: 10px;
  216. width: 0;
  217. visibility: visible;
  218. transition: all 0.3s ease;
  219. display: flex;
  220. justify-content: center;
  221. align-items: center;
  222. image {
  223. width: 20px;
  224. height: 6px;
  225. }
  226. }
  227. }
  228. </style>