Vue 简单自定义标签
思路:
1、计算每个项离父级左侧宽 left
2、计算当前滑块的宽,绝对定位
3、下一个项的宽/2-滑块的宽/2+下一项离父级左侧的宽 left
4、使用定位left(性能较差一点) 或 translate 移动距离
<template>
<div class="tab-list">
<!--菜单-->
<div class="tab-list-main">
<div class="tab-list-main-item"
v-for="(item,index) in list"
:key="item.component"
:class="{ 'tab-list-main-active': index === currentIndex }"
@click="changeItem(index)"
>
{{item.title}}
</div>
<!--滑块-->
<div class="tab-list-main-slider" id="lineSlider"></div>
</div>
</div>
</template>
export default {
name: "oil-tabs",
props: {
list: { // 菜单
type: Array,
default: () => []
},
duration: { // 滑动所需时间 单位ms
type: Number,
default: () => 300
},
activeColor: { // 选中项颜色
type: String,
default: () => ''
}
},
data() {
return {
currentIndex: 0,// 当前的下标
itemObj: {} // 菜单项的位置
}
},
watch: {
list: {
handler(val) {
this.itemObj = {}
this.list = val
if (val && val.length > 0) {
this.$nextTick(() => {
this.handleSlider()
})
}
}, immediate: true
}
},
methods: {
/**
* 点击tab项
* */
changeItem(index) {
if (this.currentIndex === index) {
return;
}
this.currentIndex = index
this.handleSlider()
let item = this.list[index]
this.$emit('change', item)
},
/**
* 计算菜单项的位置
* */
async handleItem() {
let obj = {}
this.itemObj = {}
let itemList = document.getElementsByClassName('tab-list-main-item');
if (itemList.length > 0) {
for (let o = 0; o < itemList.length; o++) {
obj[o] = itemList[o].getBoundingClientRect()
obj[o].offsetLeft = itemList[o].offsetLeft
}
this.itemObj = obj
}
},
/**
* 计算滑块当前位置
* */
async handleSlider() {
if (Object.keys(this.itemObj).length === 0) {
await this.handleItem()
}
let currentTab = this.itemObj[this.currentIndex]
if (!currentTab) {
return;
}
// 滑块
let lineSlider = document.getElementById('lineSlider');
if (!lineSlider) {
return;
}
// 滑块宽
let { width: currentWidth } = lineSlider.getBoundingClientRect();
// 下一个菜单的宽及距父级左侧left
let { offsetLeft, width } = currentTab;
// 滑块位置
// lineSlider.style.left = width / 2 - currentWidth / 2 + offsetLeft + 'px';
lineSlider.style.transform = `translateX(${width / 2 - currentWidth / 2 + offsetLeft}px)`;
}
}
}
.tab-list {
width: 100%;
min-height: 28px;
&-main {
display: flex;
flex-wrap: nowrap;
white-space: nowrap;
overflow: auto hidden;
color: #ccc;
position: relative;
padding-bottom: 5px;
&-item {
margin-right: 24px;
font-size: 14px;
font-weight: 500;
&:last-child {
margin-right: 0;
}
}
&-active {
color: blue;
font-weight: 800;
}
&-slider {
margin-top: 2px;
height: 3px;
width: 28px;
border-radius: 2px;
background-color: blue;
position: absolute;
bottom: 0;
left: 0;
transform: translateX(0);
will-change: left;
transition: all .1s linear;
}
}
}