parent
8866ea3313
commit
7cef51200c
@ -0,0 +1,484 @@ |
|||||||
|
<template> |
||||||
|
<view class="QS-tabs" :style="{ |
||||||
|
'z-index': zIndex, |
||||||
|
'font-size': getFontSize + 'rpx', |
||||||
|
'background-color': getBgColor, |
||||||
|
'transition-duration': getDuration + 's' |
||||||
|
}"> |
||||||
|
<scroll-view scroll-x class="QS-tabs-scroll" :scroll-left="left" scroll-with-animation :style="{ |
||||||
|
'z-index': (Number(zIndex) + 1) |
||||||
|
}"> |
||||||
|
<view class="QS-tabs-scroll-box"> |
||||||
|
<!-- 循环tabs --> |
||||||
|
<view class="QS-tabs-scroll-item" :style="{ |
||||||
|
'height':'100rpx', |
||||||
|
'line-height': getHeight + 'rpx', |
||||||
|
'min-width': getWidth + 'rpx', |
||||||
|
'padding': '0 ' + space + 'rpx', |
||||||
|
'color': index===getCurrent?getActiveColor:getDefaultColor, |
||||||
|
'font-weight': activeBold&&index===getCurrent?'bold':'', |
||||||
|
'transition-duration': getDuration + 's', |
||||||
|
'z-index': (Number(zIndex) + 2) |
||||||
|
}" |
||||||
|
v-for="(item, index) in getTabs" :key="index" @tap="emit(index)" :id="preId + index"> |
||||||
|
<!-- line1 --> |
||||||
|
<view v-if="animationMode==='line1'" class="boxStyle" :style="getDurationStyle +( index===getCurrent?getActiveStyle:getDefaultStyle)"></view> |
||||||
|
{{item.name || item}} |
||||||
|
</view> |
||||||
|
<!-- itemBackground --> |
||||||
|
<view v-if="hasItemBackground" class="itemBackgroundBox" :style="{ |
||||||
|
'height': getHeight + 'rpx', |
||||||
|
'width': (isLine3&&tabsInfo[animationFinishCurrent]?tabsInfo[animationFinishCurrent].width:tabsInfo[getCurrent].width) + 'px', |
||||||
|
'z-index': Number(zIndex) + 1, |
||||||
|
'transition-duration': getDuration + 's', |
||||||
|
'left': (tabsInfo[getCurrent]?tabsInfo[getCurrent].left:0) + 'px' |
||||||
|
}"> |
||||||
|
<view class="itemBackground" :style="'transition-duration:' + getDuration + 's;' + |
||||||
|
'background-color:' + getItemBackgroundColor + ';' + |
||||||
|
'box-shadow: 0 0 5rpx 5rpx ' + getItemBackgroundColor + ';' + |
||||||
|
itemBackgroundStyle + ';'" /> |
||||||
|
</view> |
||||||
|
<!-- line2 --> |
||||||
|
<view v-if="animationMode==='line2'" class="boxStyle2" :style="getLinezIndex + getDurationStyle + |
||||||
|
'width:' + lW + 'px;' + |
||||||
|
'background-color:' + (lineColor||getActiveColor) + ';' + |
||||||
|
line2Style + ';' + |
||||||
|
'left:' + line2Dx + 'px;'" /> |
||||||
|
|
||||||
|
<view v-if="animationMode==='line3'" class="boxStyle2" :style="getLinezIndex + |
||||||
|
'width:' + lW + 'px;' + |
||||||
|
'background-color:' + (lineColor||getActiveColor) + ';' + |
||||||
|
line2Style + ';' + |
||||||
|
'left:' + getLine3Dx + 'px'" /> |
||||||
|
|
||||||
|
</view> |
||||||
|
</scroll-view> |
||||||
|
</view> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
const { |
||||||
|
windowWidth |
||||||
|
} = uni.getSystemInfoSync(); |
||||||
|
const preId = 'QSTabsID_'; |
||||||
|
export default { |
||||||
|
props: { |
||||||
|
tabs: { //需循环的标签列表 |
||||||
|
type: Array, |
||||||
|
default () { |
||||||
|
return []; |
||||||
|
} |
||||||
|
}, |
||||||
|
current: { //当前所在滑块的 index |
||||||
|
type: Number, |
||||||
|
default: 0 |
||||||
|
}, |
||||||
|
height: { //QS-tabs的高度和行高 |
||||||
|
type: [String, Number], |
||||||
|
default: 80 |
||||||
|
}, |
||||||
|
minWidth: { //单个tab的最小宽度 //v1.4修改 |
||||||
|
type: [String, Number], |
||||||
|
default: 250 |
||||||
|
}, |
||||||
|
fontSize: { //字体大小 |
||||||
|
type: [String, Number], |
||||||
|
default: 30 |
||||||
|
}, |
||||||
|
duration: { //过渡动画时长, 单位 s |
||||||
|
type: [String, Number], |
||||||
|
default: .5 |
||||||
|
}, |
||||||
|
activeColor: { //选中项的主题颜色 |
||||||
|
type: String, |
||||||
|
default: '#33cc33' |
||||||
|
}, |
||||||
|
defaultColor: { //未选中项的主题颜色 |
||||||
|
type: String, |
||||||
|
default: '#888' |
||||||
|
}, |
||||||
|
animationLineWidth: { //动画线条的宽度 |
||||||
|
type: [String, Number], |
||||||
|
default: 20 |
||||||
|
}, |
||||||
|
line2Style: { //line2线条的样式 |
||||||
|
type: String, |
||||||
|
default: 'height: 8rpx;border-radius: 4rpx;' |
||||||
|
}, |
||||||
|
animationMode: { //动画类型 |
||||||
|
type: String, |
||||||
|
default: 'line1' |
||||||
|
}, |
||||||
|
autoCenter: { //是否自动滚动至中心目标 |
||||||
|
type: Boolean, |
||||||
|
default: true |
||||||
|
}, |
||||||
|
autoCenterMode: { //滚动至中心目标类型 |
||||||
|
type: String, |
||||||
|
default: 'component' |
||||||
|
}, |
||||||
|
activeStyle: { //line1模式选中项的样式 |
||||||
|
type: String, |
||||||
|
default: 'bottom:0;left:50%;transform: translate(-50%,-100%);height: 8rpx;border-radius:4rpx;' |
||||||
|
}, |
||||||
|
defaultStyle: { //line1模式未选中项的样式 |
||||||
|
type: String, |
||||||
|
default: 'bottom:0;left:50%;transform: translate(-50%,-100%);height: 8rpx;border-radius:4rpx;' |
||||||
|
}, |
||||||
|
backgroundColor: { //统一背景颜色 |
||||||
|
type: String, |
||||||
|
default: 'rgba(255,255,255,0)' |
||||||
|
}, |
||||||
|
hasItemBackground: { //是否开启背景追光 |
||||||
|
type: Boolean, |
||||||
|
default: false |
||||||
|
}, |
||||||
|
itemBackgroundColor: { //统一追光背景颜色 |
||||||
|
type: String, |
||||||
|
default: 'rgba(255,255,255,0)' |
||||||
|
}, |
||||||
|
itemBackgroundStyle: { //追光样式 |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
zIndex: { //css的z-index属性值 |
||||||
|
type: [String, Number], |
||||||
|
default: 99 |
||||||
|
}, |
||||||
|
swiperWidth: { //line3生效, 外部swiper的宽度, 单位rpx |
||||||
|
type: [String, Number], |
||||||
|
default: 750 |
||||||
|
}, |
||||||
|
space: { //tab间距 |
||||||
|
type: [String, Number], |
||||||
|
default: '0' |
||||||
|
}, |
||||||
|
activeBold: { //当前tab字体是否加粗 |
||||||
|
type: Boolean, |
||||||
|
default: true |
||||||
|
}, |
||||||
|
lineColor: { //line颜色 |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
} |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
isLine3() { |
||||||
|
return this.animationMode === 'line3'; |
||||||
|
}, |
||||||
|
getCurrent() { |
||||||
|
const current = Number(this.current); |
||||||
|
if (current > (this.getTabs.length - 1)) { |
||||||
|
return (this.getTabs.length - 1) |
||||||
|
} |
||||||
|
return current; |
||||||
|
}, |
||||||
|
getTabs() { |
||||||
|
return this.tabs; |
||||||
|
}, |
||||||
|
getHeight() { |
||||||
|
return Number(this.height); |
||||||
|
}, |
||||||
|
getWidth() { |
||||||
|
return Number(this.minWidth); |
||||||
|
}, |
||||||
|
getFontSize() { |
||||||
|
return this.fontSize; |
||||||
|
}, |
||||||
|
getDuration() { |
||||||
|
return Number(this.duration); |
||||||
|
}, |
||||||
|
getBgColor() { |
||||||
|
const defaultColor = this.backgroundColor || 'rgba(255,255,255,0)'; |
||||||
|
if (this.getTabs[this.getCurrent] instanceof Object) { |
||||||
|
return this.getTabs[this.getCurrent].backgroundColor || defaultColor; |
||||||
|
} else { |
||||||
|
return defaultColor; |
||||||
|
} |
||||||
|
}, |
||||||
|
getItemBackgroundColor() { |
||||||
|
const defaultColor = this.itemBackgroundColor || 'rgba(255,255,255,0)'; |
||||||
|
if (this.getTabs[this.getCurrent] instanceof Object) { |
||||||
|
return this.getTabs[this.getCurrent].itemBackgroundColor || defaultColor; |
||||||
|
} else { |
||||||
|
return defaultColor; |
||||||
|
} |
||||||
|
}, |
||||||
|
getDurationStyle() { |
||||||
|
return `transition-duration: ${this.getDuration}s;` |
||||||
|
}, |
||||||
|
getActiveColor() { |
||||||
|
let activeColor; |
||||||
|
if (this.getTabs[this.getCurrent] instanceof Object) { |
||||||
|
if (this.getTabs[this.getCurrent].activeColor) { |
||||||
|
activeColor = this.getTabs[this.getCurrent].activeColor; |
||||||
|
} else { |
||||||
|
activeColor = this.activeColor; |
||||||
|
} |
||||||
|
} else { |
||||||
|
activeColor = this.activeColor; |
||||||
|
} |
||||||
|
return activeColor; |
||||||
|
}, |
||||||
|
getDefaultColor() { |
||||||
|
let defaultColor; |
||||||
|
if (this.getTabs[this.getCurrent] instanceof Object) { |
||||||
|
if (this.getTabs[this.getCurrent].defaultColor) { |
||||||
|
defaultColor = this.getTabs[this.getCurrent].defaultColor; |
||||||
|
} else { |
||||||
|
defaultColor = this.defaultColor; |
||||||
|
} |
||||||
|
} else { |
||||||
|
defaultColor = this.defaultColor; |
||||||
|
} |
||||||
|
return defaultColor; |
||||||
|
}, |
||||||
|
getActiveStyle() { |
||||||
|
return `width:${this.animationLineWidth}%;background-color:${this.getActiveColor};${this.activeStyle};`; |
||||||
|
}, |
||||||
|
getDefaultStyle() { |
||||||
|
return `width:0;background-color:${this.getActiveColor};${this.defaultStyle};`; |
||||||
|
}, |
||||||
|
getLinezIndexNum() { |
||||||
|
return Number(this.zIndex) + 2; |
||||||
|
}, |
||||||
|
getLinezIndex() { |
||||||
|
return `z-index: ${this.getLinezIndexNum};`; |
||||||
|
}, |
||||||
|
getLine3Dx() { |
||||||
|
return Number(this.line3Dx) + Number(this.line3AddDx); |
||||||
|
} |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
current(n, o) { |
||||||
|
this.change(n); |
||||||
|
}, |
||||||
|
tabs() { |
||||||
|
this.init(); |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
left: 0, |
||||||
|
tabsInfo: [], |
||||||
|
line2Width: Number(this.animationLineWidth), |
||||||
|
setTimeoutFc: null, |
||||||
|
componentsWidth: 0, |
||||||
|
animationFinishCurrent: this.current, |
||||||
|
pxWidth: 0, |
||||||
|
lW: 0, |
||||||
|
sW: 0, |
||||||
|
preId, |
||||||
|
line3Dx: 0, |
||||||
|
line3AddDx: 0, |
||||||
|
line2Dx: 0 |
||||||
|
} |
||||||
|
}, |
||||||
|
// #ifndef H5 |
||||||
|
onReady() { |
||||||
|
this.init(); |
||||||
|
}, |
||||||
|
// #endif |
||||||
|
// #ifdef H5 |
||||||
|
mounted() { |
||||||
|
this.init(); |
||||||
|
}, |
||||||
|
// #endif |
||||||
|
methods: { |
||||||
|
init() { |
||||||
|
this.countPx(); |
||||||
|
let view = uni.createSelectorQuery().in(this); |
||||||
|
for (let i = 0; i < this.tabs.length; i++) { |
||||||
|
view.select('#' + preId + i).boundingClientRect(); |
||||||
|
} |
||||||
|
view.exec((res) => { |
||||||
|
const arr = []; |
||||||
|
for (let i = 0; i < res.length; i++) { |
||||||
|
arr.push(res[i]); |
||||||
|
} |
||||||
|
this.tabsInfo = arr; |
||||||
|
this.countLine2Dx(); |
||||||
|
this.countLine3Dx(); |
||||||
|
let _this = this; |
||||||
|
_this.getQuery(() => { |
||||||
|
_this.countScrollX(); |
||||||
|
}); |
||||||
|
}) |
||||||
|
}, |
||||||
|
countLine2Dx() { |
||||||
|
if (this.animationMode === 'line2') { |
||||||
|
const tab = this.tabsInfo[this.getCurrent]; |
||||||
|
if(tab) this.line2Dx = tab.left + tab.width / 2 - this.lW / 2; |
||||||
|
} |
||||||
|
}, |
||||||
|
countLine3Dx() { |
||||||
|
if (this.animationMode === 'line3') { |
||||||
|
const tab = this.tabsInfo[this.animationFinishCurrent]; |
||||||
|
if(tab) this.line3Dx = tab.left + tab.width / 2 - this.lW / 2; |
||||||
|
} |
||||||
|
}, |
||||||
|
countPx() { |
||||||
|
const w = uni.upx2px(this.getWidth); |
||||||
|
this.pxWidth = w; |
||||||
|
this.lW = (w * (Number(this.animationLineWidth) / 100))+72.5; |
||||||
|
this.sW = uni.upx2px(Number(this.swiperWidth)); |
||||||
|
}, |
||||||
|
emit(index) { |
||||||
|
this.$emit('change', index); |
||||||
|
}, |
||||||
|
change() { |
||||||
|
this.countScrollX(); |
||||||
|
if (this.animationMode === 'line2') { |
||||||
|
this.line2Width = 2; |
||||||
|
if (this.setTimeoutFc) clearTimeout(this.setTimeoutFc); |
||||||
|
this.setTimeoutFc = setTimeout(() => { |
||||||
|
this.line2Width = this.animationLineWidth; |
||||||
|
}, this.getDuration * 1000 * 3 / 5); |
||||||
|
this.countLine2Dx(); |
||||||
|
} |
||||||
|
}, |
||||||
|
getQuery(cb) { |
||||||
|
try { |
||||||
|
let view = uni.createSelectorQuery().in(this).select('.QS-tabs'); |
||||||
|
view.fields({ |
||||||
|
size: true |
||||||
|
}, data => { |
||||||
|
if (data) { |
||||||
|
this.componentsWidth = data.width; |
||||||
|
if (cb && typeof cb === 'function') cb(data); |
||||||
|
} else { |
||||||
|
this.retryGetQuery(cb); |
||||||
|
} |
||||||
|
}).exec(); |
||||||
|
} catch (e) { |
||||||
|
//TODO handle the exception |
||||||
|
this.componentsWidth = windowWidth; |
||||||
|
} |
||||||
|
}, |
||||||
|
retryGetQuery(cb) { |
||||||
|
try { |
||||||
|
let view = uni.createSelectorQuery().select('.QS-tabs'); |
||||||
|
view.fields({ |
||||||
|
size: true |
||||||
|
}, data => { |
||||||
|
if (data) { |
||||||
|
this.componentsWidth = data.width; |
||||||
|
} else { |
||||||
|
this.componentsWidth = windowWidth; |
||||||
|
} |
||||||
|
if (cb && typeof cb === 'function') cb(data); |
||||||
|
}).exec(); |
||||||
|
} catch (e) { |
||||||
|
//TODO handle the exception |
||||||
|
this.componentsWidth = windowWidth; |
||||||
|
} |
||||||
|
}, |
||||||
|
countScrollX() { |
||||||
|
if (this.autoCenter) { |
||||||
|
let tab; |
||||||
|
if(this.isLine3) { |
||||||
|
tab = this.tabsInfo[this.animationFinishCurrent]; |
||||||
|
}else{ |
||||||
|
tab = this.tabsInfo[this.getCurrent]; |
||||||
|
} |
||||||
|
if(tab) { |
||||||
|
let tabCenter = tab.left + tab.width/2; |
||||||
|
let fatherWidth; |
||||||
|
if (this.autoCenterMode === 'window') { |
||||||
|
fatherWidth = windowWidth; |
||||||
|
} else { |
||||||
|
fatherWidth = this.componentsWidth; |
||||||
|
} |
||||||
|
this.left = tabCenter - fatherWidth / 2; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
setDx(dx) { |
||||||
|
const tab = this.tabsInfo[dx>0?(this.animationFinishCurrent + 1):(this.animationFinishCurrent - 1)]; |
||||||
|
this.line3AddDx = dx / this.sW * (tab?tab.width:this.pxWidth); |
||||||
|
}, |
||||||
|
setFinishCurrent(current) { |
||||||
|
this.line3AddDx = 0; |
||||||
|
this.animationFinishCurrent = current; |
||||||
|
this.countLine3Dx(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
view, |
||||||
|
scroll-view { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
.QS-tabs { |
||||||
|
width: 100%; |
||||||
|
transition-property: background-color, color; |
||||||
|
} |
||||||
|
|
||||||
|
.QS-tabs::-webkit-scrollbar { |
||||||
|
display: none; |
||||||
|
width: 0 !important; |
||||||
|
height: 0 !important; |
||||||
|
-webkit-appearance: none; |
||||||
|
background: transparent; |
||||||
|
} |
||||||
|
|
||||||
|
.QS-tabs-scroll { |
||||||
|
width: 100%; |
||||||
|
white-space: nowrap; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.QS-tabs-scroll-box { |
||||||
|
position: relative; |
||||||
|
display: flex; |
||||||
|
white-space: nowrap !important; |
||||||
|
display: block !important; |
||||||
|
} |
||||||
|
|
||||||
|
.QS-tabs-scroll-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; |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.itemBackground { |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
transition-property: all; |
||||||
|
} |
||||||
|
</style> |
Before Width: | Height: | Size: 405 KiB After Width: | Height: | Size: 153 KiB |
Loading…
Reference in new issue