Vue中简单使用Swiper

本文介绍了在Vue项目中如何简单封装Swiper组件,解决Vue-Awesome-Swiper存在的问题,包括异步数据渲染导致的尺寸计算错误、swiper-wrapper额外类名设置以及事件处理。通过封装,可以更灵活地控制Swiper实例,并在需要时动态初始化和销毁。

Swiper 是纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端。中文版文档见Swiper中文网
在应用到Vue的工程中时,有 Vue-Awesome-Swiper 可用,但是也发现一些问题,最后自己简单封装一下Swiper绕过。

先看原始版直接在Vue中使用:

<!DOCTYPE html>
<html lang="zh-CN" >
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <title>swiper demo</title>
    <link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/css/swiper.min.css">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.8/vue.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/js/swiper.min.js"></script>

    <style type="text/css">
    html, body {position: relative; height: 100%; font-size: 18px; }
    body {margin: 0; padding: 0; }
    #app{height: 100%;}
    .swiper-container {width: 100%; height: 100%; }
    .swiper-slide { background: #fff; display: flex; justify-content: center; align-items: center; }
    </style>
  </head>

  <body>
    <div id="app">
        <div class="swiper-container">
            <div class="swiper-wrapper">
                <div v-for="(c, i) in bgColors" class="swiper-slide" :style="{background: c}">Slide {{i+1}}</div>
            </div>
            <div class="swiper-pagination"></div>
        </div>
    </div>
    <script type="text/javascript">
        var app = new Vue({
            el: '#app',
            data:{
                bgColors: ['Linen', 'Cornsilk', 'FloralWhite', 'LemonChiffon', 'MistyRose']
            },
            mounted: function(){
                this.$nextTick(function(){
                    swiper = new Swiper('.swiper-container',{
                        pagination: {
                            el: '.swiper-pagination'
                        }
                    });
                });
            }
        });
    </script>
  </body>
</html>

swiper的实例会附加在dom节点上,在vue内部可以通过this.$el.swiper访问到。只不过使用swiper比较多的话,需要在各种地方写很多实例化swiper的代码。把swiper封装成一个vue组件就很有必要了,而且有现成的可用。

下面是使用vue-awesome-swiper的代码:

<script type="text/javascript" src="dist/js/vue-awesome-swiper.js"></script>
    <div id="app">
        <swiper :options="swiperOption">
            <swiper-slide v-for="(c, i) in bgColors" :style="{background: c}">Slide {{i+1}}</swiper-slide>
            <div class="swiper-pagination" slot="pagination"></div>
        </swiper>
    </div>
    <script type="text/javascript">
        Vue.use(window.VueAwesomeSwiper)
        var app = new Vue({
            el: '#app',
            data:{
                bgColors: ['Linen', 'Cornsilk', 'FloralWhite', 'LemonChiffon', 'MistyRose'],
                swiperOption: {
                  pagination: {
                    el: '.swiper-pagination'
                  }
                }
            }
        });
    </script>

但是如果异步加载的数据,延时渲染swiper-slide中的内容,会导致width或者height初始计算不对,就算再调用swiper.update()已无法更新。而且如果需要在swiper-wrapper同级加入元素只能放置在slot为pagination, button-prev, button-next, scrollbar 中,而且无法给swiper-wrapper所在的节点增加额外的class。于是借鉴vue-awesome-swiper的思路自己来一个。

    <div id="app">
        <swiper :options="swiperOption">
            <div class="swiper-slide" v-for="(c, i) in bgColors" :style="{background: c}">Slide {{i+1}}</div>
            <template #footer>
                <div class="swiper-pagination"></div>
            </template>
        </swiper>
    </div>
    <script type="text/javascript">
        Vue.component('swiper', {
            template:`
            <div class="swiper-container">      
                <slot name="header"></slot>
                <div class="swiper-wrapper" :class="extWrapperClass">
                    <slot></slot>
                </div>
                <slot name="footer"></slot>
            </div>
            `,
            props: ['options', 'extWrapperClass'],
            data (){
                return {
                }
            },
            mounted: function(){
                this.update();
            },
            updated (){
                this.update();
            },
            beforeDestroy() {
                this.$nextTick(function() {
                    if (this.$el.swiper && this.$el.swiper.destroy) {
                       this.$el.swiper.destroy()
                    }
                })
            },
            methods: {
               init (){
                    var opts = Object.assign({}, this.options);
                    if(opts.on){
                        Object.keys(opts.on).forEach(key => {
                            opts.on[key] = opts.on[key].bind(this);
                        });
                    }
                    var swiper = new Swiper(this.$el, opts);
                    return swiper;
               },
               update () {
                    this.$nextTick(()=>{
                        this.init();
                    });
               }
            }
        });
        var app = new Vue({
            el: '#app',
            data:{
                bgColors: ['Linen', 'Cornsilk', 'FloralWhite', 'LemonChiffon', 'MistyRose'],
                swiperOption: {
                  pagination: {
                    el: '.swiper-pagination'
                  }
                }
            }
        });
    </script>

增加了extWrapperClass属性可以可swiper-wrapper增加其他的class了。事件的监听不用vue代理一层,而直接交给swiper,并且切换了上下文为vue实例,即事件方法里this本来指向swiper实例,现在指向vue实例,方便访问vue。如果要访问swiper实例,用this.$el.swiper可以访问到。

swiperOption:{
    pagination: {
        el: '.swiper-pagination'
    },
    on: {
        touchMove: function(e){
            console.log(this.$el.swiper.translate);
        }
    }
}

在使用swiper的自动播放autoplay和循环loop两个特性时,如果动态的swiper-slide只有一个就比较尴尬了。甚至在有时候一个swiper-slide也没有,根本就不需要swiper实例的存在。于是需要在合适的时候初始化swiper,并在一些时候销毁swiper。

完整版代码如下:

<!DOCTYPE html>
<html lang="zh-CN" >
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <title>swiper demo</title>
    <link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/css/swiper.min.css">

    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.8/vue.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/js/swiper.min.js"></script>
    <style type="text/css">
    html, body {position: relative; height: 100%; font-size: 18px; }
    body {margin: 0; padding: 0; }
    #app{height: 100%;}
    .swiper-container {width: 100%; height: 100%; }
    .swiper-slide { background: #fff; display: flex; justify-content: center; align-items: center; }
    </style>
  </head>

  <body>
    <div id="app">
        <swiper :options="swiperOption">
            <div class="swiper-slide" v-for="(c, i) in bgColors" :style="{background: c}">Slide {{i+1}}</div>
            <template #footer>
                <div class="swiper-pagination"></div>
            </template>
        </swiper>
    </div>
    <script type="text/javascript">
        Vue.component('swiper', {
            template:`
            <div class="swiper-container">      
                <slot name="header"></slot>
                <div class="swiper-wrapper" :class="extWrapperClass">
                    <slot></slot>
                </div>
                <slot name="footer"></slot>
            </div>
            `,
            props: ['options', 'extWrapperClass'],
            data (){
                return {
                }
            },
            mounted: function(){
                this.update();
            },
            updated (){
                this.update();
            },
            beforeDestroy() {
                this.$nextTick(function() {
                    if (this.$el.swiper && this.$el.swiper.destroy) {
                       this.$el.swiper.destroy()
                    }
                })
            },
            methods: {
               init (){
                    var opts = Object.assign({}, this.options);
                    if(opts.on){
                        Object.keys(opts.on).forEach(key => {
                            opts.on[key] = opts.on[key].bind(this);
                        });
                    }
                    var swiper = new Swiper(this.$el, opts);
                    return swiper;
               },
               update () {
                    this.$nextTick(()=>{
                        //根据slide的数量决定swiper的初始化及更新
                        var len = this.$el.querySelectorAll('.swiper-wrapper:first-of-type > .swiper-slide').length;
                        var swiper = this.$el.swiper;

                        if(len == 0) {
                            if(swiper){
                                swiper.destroy();
                            }
                            return;
                        }

                        if(swiper){
                            swiper.update();
                        }else{
                            swiper = this.init();
                        }

                        if(this.options.autoplay){
                            if( this.options.loop ? len > 3 : len > 1){
                                swiper.autoplay.start();
                            }else{
                                swiper.autoplay.stop();
                            }
                        }
                    });
               }
            }
        });
        var app = new Vue({
            el: '#app',
            data:{
                bgColors: ['Linen', 'Cornsilk', 'FloralWhite', 'LemonChiffon', 'MistyRose'],
                swiperOption: {
                  pagination: {
                    el: '.swiper-pagination'
                  }
                }
            }
        });
    </script>
  </body>
</html>

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值