nuxt4 + nuxt-swiper实现官网全屏播放

356 阅读5分钟
项目背景

楼主最近在做一个官网项目,风格偏酷炫, 页面需要实现全屏和流畅切换。 经过调研,确定使用技术栈 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: {
    
  }
}
组件使用

先放上效果图

image.png
// 完整代码
<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>