项目背景
楼主最近在做一个官网项目,风格偏酷炫, 页面需要实现全屏和流畅切换。 经过调研,确定使用技术栈 nuxt4 + nuxt-swiper 。
为什么选择nuxt ?
因为这是官网,对SEO有一定的要求。
为什么不选用fullpage ?
因为fullpage4需要商业许可证,控制台报错提示需要licenseKey。 且官网页面有多个滚动设计, 从风险规避和灵活性两个角度分析, 放弃fullpage 。
经过思考, 决定使用nuxt的配套插件nuxt-swiper
这篇文章主要是记录一下nuxt-swiper的使用。
因为使用的nuxt-swiper版本和nuxt版本的问题,插件在使用的过程中会报错。
报错1 : SwiperSlide 没有注册
修复 : 要在import { Swiper, SwiperSlide } from 'swiper/vue'中引入
报错2 :Autoplay 不存在
修复: 没有全局配置,相关模块需要显示引入import { Navigation, Pagination, Autoplay } from 'swiper/modules'
nuxt-swiper使用
安装
npm install nuxt-swiper
# 或
yarn add nuxt-swiper
Nuxt 配置
// nuxt.config.js
export default {
modules: [
'nuxt-swiper'
],
// 可选:全局配置,
// 不在这里做全局配置, 我的vscode会报错
swiper: {
}
}
组件使用
先放上效果图
// 完整代码
<template>
<div class="container3 c-container">
<div class="max-w-[1400px] mx-auto">
<PcLayoutHeader title="游戏特色" />
<div class="image-slider-container">
<template v-if="list.length > 0">
<Swiper class="image-content w-full" ref="swiperRef" :modules="modules" :slides-per-view="1"
:space-between="30" :loop="true" :speed="600" :autoplay="autoplay" :pagination="pagination"
:navigation="navigation">
<SwiperSlide v-for="(image) in list" :key="image.id">
<div class="image-wrapper">
<img :src="image.image_url" alt='' class="slider-image" loading="lazy" />
</div>
</SwiperSlide>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</Swiper>
<div class="custom-pagination"></div>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Navigation, Pagination, Autoplay } from 'swiper/modules'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import 'swiper/css/autoplay'
// Swiper 配置
const modules = [Navigation, Pagination, Autoplay]
const autoplay = {
delay: 3000,
disableOnInteraction: false,
pauseOnMouseEnter: true
}
const pagination = {
el: '.custom-pagination',
clickable: true,
type: 'bullets',
bulletClass: 'custom-bullet',
bulletActiveClass: 'custom-bullet-active',
renderBullet: (index, className) => {
return `<span class="${className}"></span>`
}
}
const navigation = {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
const { list } = useSection3List()
</script>
<style lang="scss">
// page3 背景图
.container3 {
background: url(/assets/image/page3/bg.png) no-repeat center;
background-size: cover
}
// 容器
.image-slider-container {
height: 78vh;
transform: translateY(-2vh);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
// 轮播图片-容器
.image-content {
aspect-ratio: 2.3/1;
.image-wrapper {
position: relative;
height: 100%;
aspect-ratio: 1.78/1;
margin: 0 auto;
background: url(/assets/image/page3/swiper-bg.png) no-repeat bottom left;
background-size: contain;
}
.slider-image {
position: absolute;
width: 84.7%;
aspect-ratio: 1.78/1;
top: 10.5%;
left: 8.2%;
border: 2px solid #fff;
}
}
// 自定义导航栏
.custom-pagination {
height: 8vh;
display: flex;
justify-content: center;
align-items: center;
.custom-bullet {
max-width: 32px;
max-height: 32px;
width: 4vh;
height: 4vh;
border-radius: 50%;
background: url(/assets/image/page3/dot.png) no-repeat bottom left;
background-size: contain;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0;
margin: 0 12px;
&:hover {
box-shadow: 0px 0px 30px 1px rgba(20, 172, 188, 1);
transform: scale(1.3);
}
}
.custom-bullet-active {
background: url(/assets/image/page3/dot-active.png) no-repeat bottom left;
background-size: contain;
transform: scale(1.3);
}
}
.swiper-button-next,
.swiper-button-prev {
max-width: 120px;
max-height: 120px;
width: 13vh;
height: 13vh;
cursor: pointer;
transition: all 0.3s ease;
position: absolute;
transform: translateY(-20%);
background: url(/assets/image/page3/arrow-l.png) no-repeat bottom left;
background-size: contain;
&:hover {
transform: translateY(-20%) scale(1.1);
}
}
.swiper-button-next {
background: url(/assets/image/page3/arrow-r.png) no-repeat bottom right;
background-size: contain;
}
</style>
配置项分类
基础配置
const swiperOptions = {
// 幻灯片显示数量
slidesPerView: 1,
// 幻灯片间距
spaceBetween: 30,
// 循环模式
loop: true,
// 居中幻灯片
centeredSlides: false,
// 方向
direction: 'horizontal', // 'horizontal' | 'vertical'
// 速度
speed: 300,
// 启用CSS模式(使用CSS变换)
cssMode: false
}
分页配置
const paginationOptions = {
pagination: {
// 分页器类型
type: 'bullets', // 'bullets' | 'fraction' | 'progressbar' | 'custom'
// 可点击
clickable: true,
// 动态分页
dynamicBullets: false,
// 自定义分页HTML
renderBullet: function (index, className) {
return '<span class="' + className + '">' + (index + 1) + '</span>'
},
// 分页器位置
el: '.swiper-pagination'
}
}
导航配置
const navigationOptions = {
navigation: {
// 下一页按钮
nextEl: '.swiper-button-next',
// 上一页按钮
prevEl: '.swiper-button-prev',
// 隐藏不可用按钮
hideOnClick: true,
// 禁用类
disabledClass: 'swiper-button-disabled'
}
}
自动播放配置
const autoplayOptions = {
autoplay: {
// 延迟时间
delay: 5000,
// 用户交互后是否停止
disableOnInteraction: false,
// 暂停自动播放
pauseOnMouseEnter: true,
// 等待过渡完成
waitForTransition: true,
// 反向播放
reverseDirection: false,
// 停止在最后一张
stopOnLastSlide: false
}
}
响应式配置
const responsiveOptions = {
breakpoints: {
// 当屏幕宽度 >= 320px
320: {
slidesPerView: 1,
spaceBetween: 10
},
// 当屏幕宽度 >= 768px
768: {
slidesPerView: 2,
spaceBetween: 20
},
// 当屏幕宽度 >= 1024px
1024: {
slidesPerView: 3,
spaceBetween: 30
},
// 当屏幕宽度 >= 1440px
1440: {
slidesPerView: 4,
spaceBetween: 40
}
}
}
特效配置
// 淡入淡出效果
const fadeEffectOptions = {
effect: 'fade',
fadeEffect: {
crossFade: true
}
}
// 立方体效果
const cubeEffectOptions = {
effect: 'cube',
cubeEffect: {
slideShadows: true,
shadow: true,
shadowOffset: 20,
shadowScale: 0.94
}
}
// 封面流效果
const coverflowEffectOptions = {
effect: 'coverflow',
coverflowEffect: {
rotate: 50,
stretch: 0,
depth: 100,
modifier: 1,
slideShadows: true
}
}
常用事件和方法
// 事件处理
const eventHandlers = {
onSwiper: (swiper) => {
console.log('Swiper初始化完成', swiper)
},
onSlideChange: (swiper) => {
console.log('幻灯片切换', swiper.activeIndex)
},
onSlideChangeTransitionStart: (swiper) => {
console.log('切换过渡开始')
},
onSlideChangeTransitionEnd: (swiper) => {
console.log('切换过渡结束')
},
onAutoplay: (swiper) => {
console.log('自动播放')
}
}
// 常用方法
const swiperMethods = {
slideNext: () => swiperInstance.value.slideNext(),
slidePrev: () => swiperInstance.value.slidePrev(),
slideTo: (index) => swiperInstance.value.slideTo(index),
update: () => swiperInstance.value.update(),
destroy: () => swiperInstance.value.destroy()
}
二次更新
要想知道nuxt-swiper的具体属性和支持的方法,从示例对象中获取是最方便的。
例如 :
<Swiper :modules="modules" :direction="'vertical'" :slides-per-view="1" :space-between="0" :speed="slideSpeed" :mousewheel="true" :keyboard="{ enabled: true }" @swiper="onSwiper" @slide-change="onSlideChange" class="h-[100vh]">
<SwiperSlide>
<PcSectionsSection1 /> <!--第一屏-->
</SwiperSlide>
<SwiperSlide>
<PcSectionsSection2 /> <!--第二屏-->
</SwiperSlide>
<SwiperSlide class="page3">
<div ref="page3" @wheel="handleScroll" style="height:100vh; overflow-y: auto;" class="page3">
<PcSectionsSection3 /> <!--第三屏-->
<PcLayoutFooter /> <!--这个是页脚,只有大约300px-->
</div>
</SwiperSlide>
</Swiper>
<script setup>
import { Swiper, SwiperSlide } from 'swiper/vue' // 引入组件
import { Mousewheel, Keyboard } from 'swiper/modules' // 引入控件
import 'swiper/css' // 样式
const modules = [Mousewheel, Keyboard]
const slideSpeed = 500 // 轮播速度
const swiperInstance = ref(null) // 轮播插件实例
// 获取 Swiper 实例 - 正确的方式
const onSwiper = (swiper) => {
swiperInstance.value = swiper
}
// 滚动回调,注意, isEnd = true说明是最后一屏了, 禁止滚动操作, 确保最后一屏的组件可以触发滚动事件
const onSlideChange = (swiper) => {
if (swiper.isEnd) {
swiperInstance.value.enabled = false
}
}
// 跳转到指定页面
const goToSlide = (index) => {
if (swiperInstance.value) {
swiperInstance.value.enabled = true
swiperInstance.value.slideTo(index, slideSpeed)
}
}
// 只有插件禁止滚动后,滚动条事件才会被触发
// 滚动监听,如果是向下滚动,则继续, 如果向上移动到顶部,则将插件设置为可以滚动
let isReady = true
const page3 = ref(null)
const handleScroll = (e) => {
if (isReady) {
const scrollTop = page3.value.scrollTop
if (swiperInstance.value.isEnd && e.wheelDeltaY > 0 && scrollTop <= 0) {
swiperInstance.value.enabled = true
}
isReady = false
setTimeout(() => {
isReady = true
}, 500)
}
}
</script>
<style scoped lang="scss">
// 这部分样式可以隐藏元素滚动条
.page3 {
scrollbar-width: none;
-ms-overflow-style: none;
}
.page3::-webkit-scrollbar {
display: none;
}
</style>