<template>
<view class="u-tabs" :style="{
zIndex: zIndex,
background: bgColor
}">
<scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{ zIndex: zIndex + 1 }">
<view class="u-tabs-scroll-box" :class="{'u-tabs-scorll-flex': !isScroll}">
<view class="u-tabs-item" :style="[tabItemStyle(index)]"
v-for="(item, index) in getTabs" :key="index" :class="[preId + index]" @tap="emit(index)">
<u-badge :count="item[count] || item['count'] || 0" :offset="offset" size="mini"></u-badge>
{{ item[name] || item['name']}}
</view>
<view v-if="showBar" class="u-scroll-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</template>
<script>
import colorGradient from '../../libs/function/colorGradient';
let color = colorGradient;
const { windowWidth } = uni.getSystemInfoSync();
const preId = 'UEl_';
export default {
name: "u-tabs-swiper",
props: {
isScroll: {
type: Boolean,
default: true
},
list: {
type: Array,
default () {
return [];
}
},
current: {
type: [Number, String],
default: 0
},
height: {
type: [Number, String],
default: 80
},
fontSize: {
type: [Number, String],
default: 30
},
swiperWidth: {
type: [String, Number],
default: 750
},
activeColor: {
type: String,
default: '#2979ff'
},
inactiveColor: {
type: String,
default: '#303133'
},
barWidth: {
type: [Number, String],
default: 40
},
barHeight: {
type: [Number, String],
default: 6
},
gutter: {
type: [Number, String],
default: 40
},
zIndex: {
type: [Number, String],
default: 1
},
bgColor: {
type: String,
default: '#ffffff'
},
autoCenterMode: {
type: String,
default: 'window'
},
name: {
type: String,
default: 'name'
},
count: {
type: String,
default: 'count'
},
offset: {
type: Array,
default: () => {
return [5, 20]
}
},
bold: {
type: Boolean,
default: true
},
activeItemStyle: {
type: Object,
default() {
return {}
}
},
showBar: {
type: Boolean,
default: true
},
barStyle: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
scrollLeft: 0,
tabQueryInfo: [],
windowWidth: 0,
animationFinishCurrent: this.current,
componentsWidth: 0,
line3AddDx: 0,
line3Dx: 0,
preId,
sW: 0,
tabsInfo: [],
colorGradientArr: [],
colorStep: 100
};
},
computed: {
getCurrent() {
const current = Number(this.current);
if (current > this.getTabs.length - 1) {
return this.getTabs.length - 1;
}
if (current < 0) return 0;
return current;
},
getTabs() {
return this.list;
},
scrollBarLeft() {
return Number(this.line3Dx) + Number(this.line3AddDx);
},
barWidthPx() {
return uni.upx2px(this.barWidth);
},
tabItemStyle() {
return (index) => {
let style = {
height: this.height + 'rpx',
lineHeight: this.height + 'rpx',
padding: `0 ${this.gutter / 2}rpx`,
color: this.tabsInfo.length > 0 ? (this.tabsInfo[index] ? this.tabsInfo[index].color : this.activeColor) : this.inactiveColor,
fontSize: this.fontSize + 'rpx',
zIndex: this.zIndex + 2,
fontWeight: (index == this.getCurrent && this.bold) ? 'bold' : 'normal'
};
if(index == this.getCurrent) {
style = Object.assign(style, this.activeItemStyle);
}
return style;
}
},
tabBarStyle() {
let style = {
width: this.barWidthPx + 'px',
height: this.barHeight + 'rpx',
borderRadius: '100px',
backgroundColor: this.activeColor,
left: this.scrollBarLeft + 'px'
};
return Object.assign(style, this.barStyle);
}
},
watch: {
current(n, o) {
this.change(n);
this.setFinishCurrent(n);
},
list() {
this.$nextTick(() => {
this.init();
})
}
},
mounted() {
this.init();
},
methods: {
async init() {
this.countPx();
await this.getTabsInfo();
this.countLine3Dx();
this.getQuery(() => {
this.setScrollViewToCenter();
});
this.colorGradientArr = color.colorGradient(this.inactiveColor, this.activeColor, this.colorStep);
},
getTabsInfo() {
return new Promise((resolve, reject) => {
let view = uni.createSelectorQuery().in(this);
for (let i = 0; i < this.list.length; i++) {
view.select('.' + preId + i).boundingClientRect();
}
view.exec(res => {
const arr = [];
for (let i = 0; i < res.length; i++) {
res[i].color = this.inactiveColor;
if (i == this.getCurrent) res[i].color = this.activeColor;
arr.push(res[i]);
}
this.tabsInfo = arr;
resolve();
});
})
},
countLine3Dx() {
const tab = this.tabsInfo[this.animationFinishCurrent];
if (tab) this.line3Dx = tab.left + tab.width / 2 - this.barWidthPx / 2 - this.tabsInfo[0].left;
},
countPx() {
this.sW = uni.upx2px(Number(this.swiperWidth));
},
emit(index) {
this.$emit('change', index);
},
change() {
this.setScrollViewToCenter();
},
getQuery(cb) {
try {
let view = uni.createSelectorQuery().in(this).select('.u-tabs');
view.fields({
size: true
},
data => {
if (data) {
this.componentsWidth = data.width;
if (cb && typeof cb === 'function') cb(data);
} else {
this.getQuery(cb);
}
}
).exec();
} catch (e) {
this.componentsWidth = windowWidth;
}
},
setScrollViewToCenter() {
let tab;
tab = this.tabsInfo[this.animationFinishCurrent];
if (tab) {
let tabCenter = tab.left + tab.width / 2;
let fatherWidth;
if (this.autoCenterMode === 'window') {
fatherWidth = windowWidth;
} else {
fatherWidth = this.componentsWidth;
}
this.scrollLeft = tabCenter - fatherWidth / 2;
}
},
setDx(dx) {
let nextTabIndex = dx > 0 ? this.animationFinishCurrent + 1 : this.animationFinishCurrent - 1;
nextTabIndex = nextTabIndex <= 0 ? 0 : nextTabIndex;
nextTabIndex = nextTabIndex >= this.list.length ? this.list.length - 1 : nextTabIndex;
const tab = this.tabsInfo[nextTabIndex];
let nowTab = this.tabsInfo[this.animationFinishCurrent];
let nowTabX = nowTab.left + nowTab.width / 2;
let nextTab = this.tabsInfo[nextTabIndex];
let nextTabX = nextTab.left + nextTab.width / 2;
let distanceX = Math.abs(nextTabX - nowTabX);
this.line3AddDx = (dx / this.sW) * distanceX;
this.setTabColor(this.animationFinishCurrent, nextTabIndex, dx);
},
setTabColor(nowTabIndex, nextTabIndex, dx) {
let colorIndex = Math.abs(Math.ceil((dx / this.sW) * 100));
let colorLength = this.colorGradientArr.length;
colorIndex = colorIndex >= colorLength ? colorLength - 1 : colorIndex <= 0 ? 0 : colorIndex;
this.tabsInfo[nextTabIndex].color = this.colorGradientArr[colorIndex];
this.tabsInfo[nowTabIndex].color = this.colorGradientArr[colorLength - 1 - colorIndex];
},
setFinishCurrent(current) {
this.tabsInfo.map((val, index) => {
if (current == index) val.color = this.activeColor;
else val.color = this.inactiveColor;
return val;
});
this.line3AddDx = 0;
this.animationFinishCurrent = current;
this.countLine3Dx();
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
@mixin vue-flex($direction: row) {
display: flex;
flex-direction: $direction;
}
view,
scroll-view {
box-sizing: border-box;
}
.u-tabs {
width: 100%;
transition-property: background-color, color;
}
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
.u-scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.u-tabs-scroll-box {
position: relative;
}
.u-tabs-scorll-flex {
@include vue-flex;
justify-content: space-between;
}
.u-tabs-scorll-flex .u-tabs-item {
flex: 1;
}
.u-tabs-item {
position: relative;
display: inline-block;
text-align: center;
transition-property: background-color, color, font-weight;
}
.content {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.boxStyle {
pointer-events: none;
position: absolute;
transition-property: all;
}
.boxStyle2 {
pointer-events: none;
position: absolute;
bottom: 0;
transition-property: all;
transform: translateY(-100%);
}
.itemBackgroundBox {
pointer-events: none;
position: absolute;
top: 0;
transition-property: left, background-color;
@include vue-flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.itemBackground {
height: 100%;
width: 100%;
transition-property: all;
}
.u-scroll-bar {
position: absolute;
bottom: 4rpx;
}
</style>