自定义标签切换动画

子组件

vue 复制代码
<template>
  <div class="my-tab" ref="myTabRef">
    <div
      class="my-tab-item"
      v-for="(item, index) in tabs"
      :ref="(el) => (itemRefs[index] = el)"
      :class="{ active: activeName === item.name }"
      @click="tabClick(item)"
    >
      <span class="tab-text">{{ item.label }}</span>
    </div>

    <div class="slider" :style="sliderStyle"></div>
  </div>
</template>

<script setup>
import { ref, computed, nextTick, onMounted, watch } from 'vue'

const props = defineProps({
  tabs: {
    type: Array,
    required: true,
    default: () => [],
  },
  defaultActive: {
    type: String,
    default: '',
  },
})

const emit = defineEmits(['change'])

const myTabRef = ref(null)
const itemRefs = ref([])
const activeName = ref('')

watch(
  () => props.defaultActive,
  (val) => {
    if (val) activeName.value = val
  },
  { immediate: true }
)

onMounted(() => {
  if (!activeName.value && props.tabs.length) {
    activeName.value = props.tabs[0].name
  }
  nextTick(() => scrollToCenter())
})

const tabClick = async (item) => {
  activeName.value = item.name
  emit('change', item)
  await nextTick()
  scrollToCenter()
}

const scrollToCenter = () => {
  const nav = myTabRef.value
  if (!nav) return
  const index = props.tabs.findIndex((t) => t.name === activeName.value)
  const item = itemRefs.value[index]
  if (!item) return

  const to = item.offsetLeft - (nav.offsetWidth - item.offsetWidth) / 2
  nav.scrollLeft = to
}

const sliderStyle = computed(() => {
  const index = props.tabs.findIndex((t) => t.name === activeName.value)
  const el = itemRefs.value[index]
  if (!el) return { opacity: 0 }

  return {
    left: el.offsetLeft + 'px',
    width: el.offsetWidth + 'px',
    opacity: 1,
  }
})
</script>

<style lang="less" scoped>
.my-tab {
  position: relative;
  height: 44px;
  background: #fff;
  display: flex;
  align-items: center;
  overflow-x: auto;
  white-space: nowrap;
  scroll-behavior: smooth;
  padding: 0 10px;

  &::-webkit-scrollbar {
    width: 0;
    height: 0;
    display: none;
  }
}

.my-tab-item {
  flex: none;
  height: 44px;
  line-height: 44px;
  padding: 0 14px;
  color: #999;
  font-size: 14px;
  position: relative;
  z-index: 1;
  cursor: pointer;

  &.active {
    color: #333;
    font-weight: bold;
  }
}

.tab-text {
  display: inline-block;
}

.slider {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  background: linear-gradient(180deg, #649afc 0%, #5180f4 100%);
  border-radius: 2px;
  opacity: 0;
  transition: all 0.3s ease;
  z-index: 0;
}
.slider::before{
  content: '';
  position: absolute;
  left: 0;
  bottom: 50%;
  height: 50%;
  background: #649afc;
  border-radius: 50px;
  opacity: 0;
  transition: all 0.3s ease;
  z-index: 0;
}
</style>
```

## 父组件
```vue
<template>
  <div style="width: 500px; margin: 20px;">
    <SliderTabs
      :tabs="list"
      default-active="tabThree"
      @change="handleChange"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import SliderTabs from './ZiZuJian.vue'

// 父组件传递列表
const list = ref([
{ name: 'tabOne', label: 'tab切换显示1' },
  { name: 'tabTwo', label: 'tab切换显示2' },
  { name: 'tabThree', label: 'tab切换显示3' },
  { name: 'tabFour', label: 'tab切换显示4' },
  { name: 'tabFive', label: 'tab切换显示5' },
  { name: 'tabSix', label: 'tab切换显示6' },
  { name: 'tabSeven', label: 'tab切换显示7' },
  { name: 'tabEight', label: 'tab切换显示8' },
  { name: 'tabNine', label: 'tab切换显示9' },
  { name: 'tabTen', label: 'tab切换显示10' },
  { name: 'tabEleven', label: 'tab切换显示11' },
  { name: 'tabTwelve', label: 'tab切换显示12' },
  { name: 'tabThirteen', label: 'tab切换显示13' },
  { name: 'tabFourteen', label: 'tab切换显示14' },
  { name: 'tabFifteen', label: 'tab切换显示15' },
])

// 切换事件
const handleChange = (index) => {
  console.log('当前选中索引:', index)
}
</script>
```
相关推荐
坤盾科技1 小时前
Docker 离线地图服务器搭建实战:Node.js + OpenLayers + MBTiles
linux·javascript·arcgis·docker·node.js
Copy_Paste_Coder1 小时前
小程序失败后,换个方向,终于成功搞到收益
前端·javascript·后端
im_AMBER2 小时前
Browser Agent 开发:从浏览器插件到Electron CDP
前端·javascript·架构·electron·agent
前端若水2 小时前
选择器的威力 —— :has()、@layer、原生嵌套
前端·css·css3
阿赛工作室2 小时前
基于Vue3和TensorFlow.js的数字图像识别应用HTML单文件
javascript·html·tensorflow
万少2 小时前
公测期 0 元/月!商汤 SenseNova 免费 Token 再不领就没了
前端·javascript·后端
专注VB编程开发20年2 小时前
专业分析python底层调用与按键精灵,ah3等的对比,hookdll,内存加载,调用.net dll
开发语言·javascript·python·microsoft·php·.net
invicinble2 小时前
前端框架使用vue-cli( 第二层:工程配置层--技术栈配置层配置)
javascript·vue.js·前端框架
Json____2 小时前
前端入门练习题集-HTML/CSS/JS实战小项目15个
前端·css·html