动态类名在 <swiper-slide 的复制项中没有起作用的解决方法
错误代码
javascript
<swiper :options="swiperOption" ref="mySwiper" class="bottom-ul">
<swiper-slide v-for="(d, i) in bottomData" :key="i" class="menu-item">
<!-- @click="btnClick(d.groupName)" -->
<li
class="menu-li"
:class="{ active: activeGroup === d.groupName }"
>
<!-- <i style="">
<img :src="d.src">
</i> -->
<span>{{ d.groupName }}</span>
</li>
</swiper-slide>
核心原因是复制的 slide 是 Swiper 纯 DOM 层面的复制,不会触发 Vue 的响应式渲染,所以复制项里的 Vue 指令(如 :class)完全失效。
问题本质拆解
v-for 只渲染了真实的 slide,这些 slide 内的 <li> 受 Vue 响应式控制,active 类名正常;
Swiper 开启 loop 后,会在 DOM 层面复制真实 slide 生成 duplicate 项,但复制的只是静态 DOM 结构,没有 Vue 的响应式绑定,所以 :class 指令不会执行,active 类名自然不会出现在复制项的 <li> 上;
当切换到复制 slide 时,视觉上看到的是复制项的 <li>,但它没有 active 类,导致选中样式丢失。
解决方案:手动同步复制项的 active 类
核心思路是:监听 Swiper 的切换事件和 Vue 的 activeGroup 变化,手动给真实 slide + 复制 slide 中匹配的 <li> 添 / 删 active 类,绕过 Vue 响应式的限制。
javascript
<template>
<div>
<swiper
:options="swiperOption"
ref="mySwiper"
class="bottom-ul"
@slideChange="handleSlideChange"
>
<swiper-slide
v-for="(d, i) in bottomData"
:key="i"
class="menu-item"
:data-group-name="d.groupName" <!-- 给真实slide标记分组名,方便DOM查找 -->
>
<li
class="menu-li"
:class="{ active: activeGroup === d.groupName }"
>
<span>{{ d.groupName }}</span>
</li>
</swiper-slide>
</swiper>
</div>
</template>
<script>
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import SwiperCore from 'swiper';
export default {
components: {
Swiper,
SwiperSlide
},
data() {
return {
bottomData: [ // 示例数据
{ groupName: '分组1' },
{ groupName: '分组2' },
{ groupName: '分组3' },
{ groupName: '分组4' }
],
activeGroup: '分组2', // 默认选中的分组
swiperOption: {
centeredSlides: true, // 居中展示
loop: true, // 循环(导致复制slide)
slidesPerView: 'auto', // 自适应宽度
loopAdditionalSlides: 5, // 增加复制slide数量
initialSlide: 1, // 初始从真实slide开始
centeredSlidesBounds: false
}
};
},
watch: {
// 监听activeGroup变化,同步更新所有slide(真实+复制)的active类
activeGroup(newVal) {
this.syncActiveClass(newVal);
}
},
mounted() {
// 初始化时同步一次active类(确保复制项也有初始选中样式)
this.$nextTick(() => {
this.syncActiveClass(this.activeGroup);
});
},
methods: {
// 核心方法:同步真实slide和复制slide的active类
syncActiveClass(activeName) {
if (!this.$refs.mySwiper) return;
// 1. 先移除所有slide内li的active类(避免多个active)
document.querySelectorAll('.bottom-ul .menu-li').forEach(li => {
li.classList.remove('active');
});
// 2. 找到所有包含目标groupName的slide(真实+复制),给其li加active类
const targetSlides = document.querySelectorAll(`.bottom-ul .menu-item[data-group-name="${activeName}"]`);
targetSlides.forEach(slide => {
const li = slide.querySelector('.menu-li');
if (li) li.classList.add('active');
});
},
// Swiper切换事件:同步activeGroup和active类
handleSlideChange() {
const swiperInstance = this.$refs.mySwiper.swiper;
// 获取当前激活slide的真实索引
const realIndex = swiperInstance.realIndex;
// 更新activeGroup为当前真实slide对应的分组名
this.activeGroup = this.bottomData[realIndex].groupName;
// 同步复制slide的active类
this.syncActiveClass(this.activeGroup);
},
// 如果你有手动点击li的方法,也要同步调用syncActiveClass
btnClick(groupName) {
this.activeGroup = groupName;
this.syncActiveClass(groupName);
// 可选:点击后让对应的slide居中
const swiperInstance = this.$refs.mySwiper.swiper;
const targetIndex = this.bottomData.findIndex(d => d.groupName === groupName);
swiperInstance.slideTo(targetIndex + 1, 300, true); // +1 适配loop的索引偏移
}
}
};
</script>
<style scoped>
/* 示例样式:方便看选中效果 */
.menu-li {
list-style: none;
padding: 8px 16px;
cursor: pointer;
}
.menu-li.active {
background-color: #409eff;
color: white;
border-radius: 4px;
}
.bottom-ul {
width: 100%;
overflow: hidden;
}
.menu-item {
display: inline-block;
margin: 0 8px;
}
</style>