Code前端首页关于Code前端联系我们

Vue3里Swiper怎么加断点响应式效果还不会踩坑?

terry 5小时前 阅读数 84 #Vue

现在做移动端、PC端适配的前端项目,几乎没人离得开Swiper插件了,不过很多人从Vue2转Vue3,或者刚上手就卡断点响应式这块儿:明明照搬了官网代码,要么是轮播图尺寸不变,要么是断点没生效,甚至插件直接崩了不显示,今天就把踩过的坑、能落地的方法全捋一遍,连适配细节、特殊场景处理都有,看完绝对能搞定。

先确认Vue3和Swiper的版本适配

很多问题一开始就错在版本没对上,这是最基础的坑,必须第一个说,早期Vue3刚出的时候,Swiper还只支持Vue2版本,后来虽然有分支,但现在主流的都是用Vue3专属的Swiper Vue组件库,不是单独的Swiper核心包直接引。

去查最新的版本规则(2024年下半年更新的),记住这两点就行:

  • 如果你用的是Vue 3.2.0及以上版本,直接装最新的Swiper Vue就好,包名是swiperswiper/vue,对,没有单独的vue3-swiper或者类似的;
  • 别装低于Swiper 8的版本,因为8才正式稳定支持Vue3组合式API,低于这个的要么只能用选项式API凑,要么坑多到填不完。

装的时候要注意顺序吗?不用,npm和yarn随便装,命令是npm install swiper或者yarn add swiper,核心包和Vue组件是一起在swiper包里的,省了不少麻烦。

断点响应式的基础逻辑,别只复制代码

搞懂基础逻辑,比死记硬背官网的断点配置有用多了,以后改特殊场景也快。

Swiper Vue的断点,和纯JS版的逻辑一样,都是基于视口宽度(viewport width) 来判断用哪套配置的,而且有个关键点:Swiper的断点是向下兼容还是向上兼容? 很多人踩这个坑,比如配置了768: {slidesPerView: 2}1024: {slidesPerView: 3},明明浏览器宽度是900px,应该显示2个还是3个?

直接说结论:默认是向下兼容(max-width逻辑),不对等纯CSS的媒体查询的默认向上,纯CSS媒体查询一般写@media (min-width: 768px)是屏幕≥768用,而Swiper默认的breakpoints: {768: ...}屏幕≤768px时用这个配置? 不对等反了?等下,等我举个清晰的例子帮你理:

假设你写的配置是:

import { Swiper, SwiperSlide } from 'swiper/vue'
import { Autoplay, Pagination, Navigation, Breakpoints } from 'swiper/modules'
const swiperOptions = ref({
  modules: [Autoplay, Pagination, Navigation, Breakpoints],
  slidesPerView: 1, // 默认配置,也就是屏幕宽度小于第一个断点值时用
  spaceBetween: 10,
  breakpoints: {
    640: {
      slidesPerView: 2,
      spaceBetween: 20
    },
    1024: {
      slidesPerView: 3,
      spaceBetween: 30
    }
  }
})

这个时候各宽度对应的配置:

  • <640px:用默认的1个图,10px间距
  • 640px ≤ 宽度 < 1024px:用640断点的2个图,20px间距
  • ≥1024px:用1024断点的3个图,30px间距

哦,原来默认是向上触发(min-width逻辑),刚才差点说错,别搞反!不过Swiper Vue里可以通过breakpointsBase属性改,改成'container'就是基于Swiper容器的宽度,改成'window'就是默认的视口,有时候做嵌套组件,容器宽度和视口不一样,这个属性就很有用。

纯复制官网代码踩的常见坑,一个一个说

坑1:没有引入Breakpoints模块

这是90%刚上手的人都会犯的错!官网有时候给代码示例会把模块列表简化,但断点响应式必须单独引入Breakpoints模块,不然配置了breakpoints也完全没用,轮播图只会用默认配置。

模块引入的位置要记住:组合式API里是在组件外或者setup里用数组import,然后传给swiperOptions的modules属性;选项式API里是在components外import,然后在data里的swiperOptions.modules数组里加,刚才的代码示例已经加了,仔细看哦。

坑2:轮播图容器没有设置宽度

Swiper Vue的容器(标签)默认是自适应父元素宽度的,但如果父元素没有设置宽度,或者设置了宽度但用了flex/grid布局忘了让Swiper占满,那断点可能会判断错误,或者轮播图直接缩成一团。

怎么解决?给父元素或者标签加一个明确的宽度,或者用width: 100% + max-width配合,

.swiper-container-wrapper {
  width: 90%;
  max-width: 1200px;
  margin: 0 auto;
}

然后把放在这个wrapper里就行,别给加固定宽度,不然slidesPerView的配置会失效。

坑3:在setup里直接修改swiperOptions的响应式配置

有些朋友会在按钮点击、或者数据加载完之后,动态修改slidesPerView或者spaceBetween,但是直接修改整个ref对象的属性,有时候Swiper不会重新渲染,断点也会出问题。

正确的做法是什么?如果用的是Vue3的ref,修改单个属性可以直接赋值,比如swiperOptions.value.slidesPerView = 4,但如果要修改breakpoints里的配置,或者修改整个配置对象,最好是用深拷贝重新赋值,或者直接把swiperOptions写成reactive对象,因为ref包裹的对象修改单个属性是浅响应式,但有时候Swiper的watch机制需要更明确的更新。

比如改成reactive:

import { reactive } from 'vue'
const swiperOptions = reactive({
  modules: [Autoplay, Pagination, Navigation, Breakpoints],
  slidesPerView: 1,
  spaceBetween: 10,
  breakpoints: {
    640: {
      slidesPerView: 2,
      spaceBetween: 20
    },
    1024: {
      slidesPerView: 3,
      spaceBetween: 30
    }
  }
})
// 修改的时候直接赋值就行
swiperOptions.breakpoints[1024].slidesPerView = 4

不过还有个更稳妥的方法,就是给标签加一个key属性,key值和你要修改的配置关联起来,比如当数据加载完更新key,Swiper就会完全重新初始化,断点自然就生效了:

<Swiper :key="refreshKey" :options="swiperOptions">
  <SwiperSlide v-for="item in list" :key="item.id">
    <!-- 内容 -->
  </SwiperSlide>
</Swiper>
<script setup>
const refreshKey = ref(0)
// 数据加载完之后
const loadData = async () => {
  list.value = await api.getList()
  refreshKey.value++ // key变化,Swiper重新初始化
}
</script>

这个方法虽然有点“暴力”,但几乎能解决所有动态修改配置不生效的问题,包括数据加载后轮播图白屏、断点失效这些。

坑4:移动端和PC端的导航/分页配置不一样,直接加配置没用

很多时候我们需要PC端显示左右箭头导航,移动端只显示底部小圆点分页,或者PC端小圆点是数字分页,移动端是普通的,这时候如果只在breakpoints里配置navigation或者pagination,是没用的,因为默认情况下navigation和pagination的配置是全局的,断点里只能修改它们的内部属性,比如pagination的el、clickable这些,不能直接设置enabled: false或者true。

那怎么办?可以用组合式API的useBreakpoint钩子,这是Swiper Vue专属的,用来实时获取当前的断点状态,然后根据状态动态控制组件的显示隐藏。

useBreakpoint需要搭配<Swiper>@init事件使用,因为要拿到Swiper实例才能调用钩子:

<Swiper 
  :options="swiperOptions" 
  @init="onSwiperInit"
  ref="swiperRef"
>
  <SwiperSlide v-for="item in list" :key="item.id">
    <!-- 内容 -->
  </SwiperSlide>
  <!-- 只有当前断点≥1024px时才显示导航 -->
  <template v-if="isDesktop">
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>
  </template>
</Swiper>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { Swiper, SwiperSlide, useBreakpoint } from 'swiper/vue'
import { Autoplay, Pagination, Navigation, Breakpoints } from 'swiper/modules'
const swiperRef = ref(null)
const isDesktop = ref(false)
const destroyBreakpoint = ref(null)
const onSwiperInit = (swiper) => {
  // 初始化useBreakpoint钩子,传入swiper实例和回调函数
  destroyBreakpoint.value = useBreakpoint(swiper, (breakpoint) => {
    // breakpoint就是当前触发的断点值,比如1024,或者undefined(默认配置)
    isDesktop.value = breakpoint >= 1024
  })
}
// 组件卸载时要销毁钩子,避免内存泄漏
onUnmounted(() => {
  if (destroyBreakpoint.value) {
    destroyBreakpoint.value()
  }
})
const swiperOptions = reactive({
  modules: [Autoplay, Pagination, Navigation, Breakpoints],
  slidesPerView: 1,
  spaceBetween: 10,
  pagination: {
    el: '.swiper-pagination',
    clickable: true
  },
  // 全局导航配置,因为有时候导航DOM是动态显示的,所以可以先加上,DOM没显示的时候它也不会生效
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev'
  },
  breakpoints: {
    640: {
      slidesPerView: 2,
      spaceBetween: 20
    },
    1024: {
      slidesPerView: 3,
      spaceBetween: 30
    }
  }
})
</script>

这样就完美解决了不同断点显示不同控件的问题,而且比在breakpoints里配置更灵活,比如你还可以根据断点改变导航的样式、间距这些。

特殊场景的断点响应式处理

场景1:嵌套在Tab组件里的Swiper,切换Tab后断点失效

这个问题很多电商项目的商品详情页都会遇到,Tab切换到第二个或者第三个的时候,Swiper的容器宽度可能是0(因为Tab内容默认隐藏),所以切换回来的时候断点判断错误,轮播图尺寸不对。

解决方法其实很简单,就是刚才提到的给key属性,key值和Tab的activeIndex关联起来,切换Tab的时候key变化,Swiper重新初始化,自然就能获取到正确的容器宽度了:

<Tabs v-model="activeTab">
  <TabPane title="商品介绍" name="1">
    <Swiper :key="`swiper-intro-${activeTab}`" :options="introSwiperOptions">
      <!-- 内容 -->
    </Swiper>
  </TabPane>
  <TabPane title="商品评价" name="2">
    <Swiper :key="`swiper-review-${activeTab}`" :options="reviewSwiperOptions">
      <!-- 内容 -->
    </Swiper>
  </TabPane>
</Tabs>

场景2:需要根据设备类型(而不是视口宽度)设置不同的配置

比如不管视口宽度是多少,手机端都显示1个图,平板显示2个,PC显示3个,这时候视口宽度的断点可能不够用,因为有些平板的视口宽度和小屏PC差不多(比如iPad Pro的视口宽度是1024px)。

这时候可以结合useBreakpointnavigator.userAgent来判断设备类型,不过更稳妥的方法是用专门的设备检测库,比如ua-parser-js,然后把设备类型作为key的一部分,或者直接修改swiperOptions的配置:

import UAParser from 'ua-parser-js'
const parser = new UAParser()
const deviceType = parser.getDevice().type // 返回'mobile'、'tablet'、'desktop'或者undefined
const swiperOptions = reactive({
  modules: [Autoplay, Pagination, Navigation, Breakpoints],
  slidesPerView: deviceType === 'mobile' ? 1 : (deviceType === 'tablet' ? 2 : 3),
  spaceBetween: deviceType === 'mobile' ? 10 : (deviceType === 'tablet' ? 20 : 30),
  pagination: {
    el: '.swiper-pagination',
    clickable: true
  },
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev'
  }
})

如果还要结合视口宽度调整,可以把设备类型和useBreakpoint的回调函数结合起来,灵活度很高。

最后再检查一遍,确保没有遗漏

写完代码之后,别忘了做这几件事:

  1. 引入了所有需要的模块,特别是Breakpoints;
  2. 给Swiper的父元素或者容器加了明确的宽度;
  3. 如果有动态修改配置或者数据的情况,加了key属性;
  4. 如果有不同断点显示不同控件的情况,用了useBreakpoint钩子;
  5. 组件卸载时销毁了useBreakpoint钩子,避免内存泄漏;
  6. 在不同的设备和浏览器宽度下测试了一遍,确保断点生效。

现在你再去试试Vue3 Swiper的断点响应式,应该就不会踩坑了,要是还有其他问题,可以在评论区留言,我们一起讨论。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门