| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- <!--
- * @Author: jmy
- * @Date: 2025-12-13 12:02:41
- * @LastEditors: Please set LastEditors
- * @LastEditTime: 2025-12-19 15:02:01
- * @Description:
- -->
- <template>
- <scroll-view class="scs-scroll-navbar" scroll-x :scroll-left="scrollLeft" scroll-with-animation="true">
- <view v-for="(item, index) in tabsData" :key="index" class="nav-item"
- :class="index === tabCurrentIndex ? 'act-current' : ''" @tap="tabChange(index)" :id="'item-' + index">
- <view class="item-title">
- {{ item[nameKey] }}
- </view>
- </view>
- <view class="nav-underline" :style="underlineStyle"></view>
- </scroll-view>
- </template>
- <script>
- export default {
- name: 'scs-scroll-navbar',
- props: {
- // 导航项数据
- tabsData: {
- type: Array,
- default() {
- return [];
- },
- },
- // 当前选中的导航项索引
- tabCurrentIndex: {
- type: Number,
- default: 0,
- },
- // 导航项名称键名
- nameKey: {
- type: String,
- default: 'name',
- },
- // 导航项间距rpx
- calcGap: {
- type: Number,
- default: 40,
- },
- },
- data() {
- return {
- scrollLeft: 0,
- widthList: [],
- screenWidth: 0,
- itemPositions: [], // 存储每个导航项的位置信息
- };
- },
- computed: {
- // 计算下划线的样式
- underlineStyle() {
- if (this.itemPositions.length === 0 || this.tabCurrentIndex >= this.itemPositions.length) {
- return {};
- }
- const position = this.itemPositions[this.tabCurrentIndex];
- if (this.tabCurrentIndex === 0) {
- position.left = 1
- }
- return `width:${position.width}px;left:${position.left}px;opacity:1`;
- },
- },
- watch: {
- tabCurrentIndex: function (val) {
- this.tabScroll(val);
- },
- tabsData: function (val) {
- this.init();
- },
- },
- methods: {
- // 导航栏点击
- tabChange(index) {
- let self = this;
- self.$emit('tabChange', index);
- },
- // 导航栏滚动
- tabScroll(index) {
- var widthArr = this.widthList;
- var scrollWidth = 0;
- for (var i = 0; i < index + 1; i++) {
- scrollWidth += widthArr[i];
- }
- let currentWidth = widthArr[index];
- // scrollView 居中算法
- // 减去固定宽度位移
- // 减去选中的bar的宽度的一半
- scrollWidth -= this.screenWidth / 2;
- scrollWidth -= currentWidth / 2;
- this.scrollLeft = scrollWidth;
- },
- //滚动左侧
- tabScrollLeft() {
- this.scrollLeft = 0.5;
- setTimeout(() => {
- this.scrollLeft = 0;
- }, 500);
- },
- // 计算导航项宽度
- calculateItemWidth() {
- let widthArr = [];
- let positions = [];
- Promise.all(
- this.tabsData.map((_, index) =>
- new Promise((resolve) => {
- uni
- .createSelectorQuery()
- .in(this)
- .select(`#item-${index}`)
- .boundingClientRect((data) => {
- if (data) {
- // 在元素实际宽度基础上增加 this.calcGap 的左右 padding,保证滚动时两侧留白
- widthArr.push(data.width + this.calcGap / 2);
- positions.push({ left: data.left - 12, width: data.width });
- } else {
- // 兜底默认宽度
- widthArr.push(100);
- positions.push({ left: index * 100, width: 100 });
- }
- resolve();
- })
- .exec();
- })
- )
- ).then(() => {
- // 所有节点查询完毕,一次性赋值
- this.widthList = widthArr;
- this.itemPositions = positions;
- });
- },
- // 计算窗口宽度
- calculateWindowWidth() {
- var info = uni.getSystemInfoSync();
- this.screenWidth = info.screenWidth;
- },
- init() {
- const that = this;
- this.calculateWindowWidth();
- setTimeout(function () {
- that.calculateItemWidth();
- }, 1000);
- },
- },
- created() {
- this.init();
- },
- };
- </script>
- <style lang="scss" scoped>
- ::v-deep.uni-scroll-view::-webkit-scrollbar {
- display: none;
- }
- .scs-scroll-navbar {
- width: 100%;
- height: 90rpx;
- white-space: nowrap;
- .nav-item {
- height: 100%;
- text-align: center;
- color: #999999;
- display: inline-block;
- position: relative;
- font-size: 40rpx;
- margin-right: 32rpx;
- .item-title {
- line-height: 90rpx;
- display: inline-block;
- position: relative;
- z-index: 10;
- }
- }
- .act-current {
- color: #333333;
- font-size: 40rpx;
- font-weight: bold;
- }
- .nav-underline {
- position: relative;
- height: 16rpx;
- background: linear-gradient( 90deg, rgba(56,217,125,0.5) 0%, rgba(56,217,125,0) 100%);
- border-radius: 8rpx;
- bottom: 28rpx;
- left: 10px;
- width: 0;
- visibility: visible;
- transition: all 0.3s ease;
- }
- }
- </style>
|