11

1.前言

vue用了有一段时间了,开发的后台管理系统也趋于完善,现在时间比较算是有点空闲吧!这个空闲时间我在研究vue的另外的一些玩法,比如组件,插件等。今天,我就分享一个组件的练手项目--焦点图切换组件。这个项目是我用于vue组件练习的一个项目,当然了,代码也会提交到github(ec-slider),有空也会维护。我也想我开发的东西好用一点!现在,就是建议有需要的伙伴,可以来玩下这个项目,当练习的作用!另外,如果大家有什么建议,欢迎指点!

建议
1.下面的步骤,最好在自己本地上跑起来,根据文章的步骤,逐步完成,如果只看代码,很容易懵逼的。
2.如果不清楚哪个代码有什么作用,可能自己调试下,把代码去掉后,看下有什么影响,就很容易想出代码有什么作用了!

2.项目目录

clipboard.png

很普通,很好理解的一个目录,但还是简单的解释一下吧

node_modules:文件依赖模块(自动生成)
dist:打包文件产出目录(自动生成)
src:开发文件目录
src/components:组件文件目录
.babelrc:babel编译es6的配置文件
.gitnore:不提交到git的文件(目录)的配置文件
fontSize:设置rem算法的文件(现在没用到,忽略)
index.html:模板文件
index.js:入口文件
package.json:配置文件
README.md:说明文档
webpack.config.babel.js:webpack配置文件

3.步骤详解

3-1跑起来

这是项目的第一步(项目搭建这个,我不多说,之前的文章已经说了几次了!),现在src/components/ec-slider.vue这里输出一个‘守候’
1.首先,在src/components/ec-slider.vue里面输出‘守候’,代码如下

<template>
    <div>
        守候
    </div>
</template>
<script type="text/javascript">
    export default {
        data () {
            return {

            }
        },
        computed: {

        },
        mounted(){

        },
        props: [],
        methods: {

        }
    }
</script>

2.然后,在src/components/index.js里面设置注册组件(要带一个install方法),代码如下

import SlideImg from './ec-slider.vue'
const ecslide={
    install:function (Vue) {
        Vue.component('ec-slide',SlideImg)
    }
}
export default ecslide;

3.在入口文件,index.js里面引入并且使用组件

require("./index.html");
require("./src/sass/index.scss");
import Vue from 'vue'
//引入并且使用组件
import ecslide from './src/js/components/index';
Vue.use(ecslide);
let app6 = new Vue({
    el: "#app6",
    data: {

    },
    mounted(){
        
    }

});

4.在index.html(模板文件),输出组件

<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
        <title>Title</title>
    </head>
    <body>
    <div id="app6">
        <ec-slide></ec-slide>
    </div>
    </body>
</html>  

5.命令行输入$ npm run dev跑起来,结果完美!这几步的原理貌似没什么可多说的,都是固定式的步骤。

clipboard.png

3-2开发准备

经过上一步之后,基础就已经打好了,那么接下来就是一个开发的过程,大部分都是修改src/components/ec-slider.vue这个文件。
开发之前,大家不要急着写代码,先分析下当中的运行流程!
首先,一个焦点图切换,需要什么参数?根据下面的一个淘宝栗子,我简单分析下,就是下面这几个!
clipboard.png

list-图片列表[{src:'url',href:'https://www.baidu.com'},{src:'url',href:'http://www.163.com'}](src:图片的src,href:跳转连接,点击图片的时候)
autoplay-是否自动播放 布尔 (默认false)
type-轮播方式‘transparent’(透明度切换), 'slide'(滑动切换) (默认slide)
option-对应切换 (默认false,不显示)
time-轮播间隔时间,毫秒 (默认4000)
sildetype-过渡效果 (默认'ease'慢速开始,然后变快,然后慢速结束的过渡效果,参考:transition-timing-function
arrowurl-箭头图片链接
arrowsize-箭头尺寸‘width,height’
direction-切换方向'left'(左右) 'top'(上下) (默认:左右)

分析完了之后,就知道暂时需要这么多参数,那么接下来就是在ec-slider.vue里面,接收这些参数。父子组件传参方式,我想大家知道--props。代码如下

<template>
    <div>
        守候
    </div>
</template>
<script type="text/javascript">
    export default {
        data () {
            return {

            }
        },
        computed: {

        },
        mounted(){

        },
        props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl','arrowsize','option','direction'],
        methods: {

        }
    }
</script>

有地方接收参数,肯定要有地方传参数,就是index.html模板文件里面传

<div class="slider-left">
    <ec-slide :list='list' :autoplay="true" :type="'slide'" :option="true" :time="4000" :sildetype="'ease'" :arrowurl="'http://i1.buimg.com/1949/4d860a3067fab23b.jpg'" :arrowsize="'20,40'" :direction="'left'"></ec-slide>
</div>    

3-3样式布局

既然知道了,会接收什么参数,那下面先把样式布局,给弄好先,这个不多说,代码如下!(有些解释我也是直接打到代码上)

<template>
    <div class="switch-img-box" id="ec-slide-box">
        <div class="switch-img-type switch-img-left">
            <ul :style="{'width':ulWidth,'transition-timing-function':slideChange}">
                <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
                    <a :href="li.href?li.href:'javascript:;'">
                        <img :src="li.src" class="slider-img"/>
                    </a>
                </li>
            </ul>
        </div>
        <!--如果需要显示对应的点-->
        <div class="switch-option" v-if="option">
            <div>
                <span v-for="(li,index) in list"></span>
            </div>
        </div>
        <!--如果需要显示箭头-->
        <div class="switch-arrow" v-if="arrowurl&&arrowsize">
            <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}"
                 :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"></div>
            <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}"
                 :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"></div>
        </div>
    </div>
</template>
<script type="text/javascript">
    export default {
        data () {
            return {
                slideChange: '',
                arrowWidth: '',
                arrowHeight: '',
            }
        },
        computed: {
            //ul宽度
            ulWidth: function () {
                return (this.list.length) + "00%";

            },
            //li宽度
            listWidth: function () {
                return 100 / (this.list.length)
            }
        },
        mounted(){
            //设置各个数据初始值
            this.slideChange = this.sildetype || 'ease';
            if (this.arrowsize && this.arrowurl) {
                this.arrowWidth = this.arrowsize.split(',')[0];
                this.arrowHeight = this.arrowsize.split(',')[1];
            }
        },
        props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
        methods: {
        }
    }
</script>
<style lang="scss">
    .ec-slide-img-box {
        width: 100%;
        height: 100%;
        position: relative;
        touch-action: none;
    }

    .ec-slide-img-type {
        position: relative;
        overflow: hidden;
        width: 100%;
        height: 100%;
        &.ec-slide-img-top {
        }
        &.ec-slide-img-left {
            li {
                display: inline-block;
                font-size: 0;
            }
        }
        &.ec-slide-img-transparent {
            li {
                opacity: 0;
                transition: opacity 1s;
                width: 0;
                &.cur {
                    width: auto;
                }
                &.show {
                    opacity: 1;
                }
            }
        }
        ul {
            font-size: 0;
            &.tran {
                transition: all .4s;
            }
            li {
                text-align: center;
            }

            img {
                vertical-align: middle;
                max-width: 100%;
                max-height: 100%;
            }
        }
    }

    .ec-slide-arrow {
        div {
            position: absolute;
            z-index: 2;
            margin: auto;
            top: 0;
            bottom: 0;
            right: 0;
            left: 0;
            opacity: .5;
            &:hover {
                opacity: 1;
            }
            &.arrow-left {
                left: 10px;
                right: auto;
            }
            &.arrow-right {
                right: 10px;
                left: auto;
                transform: rotate(180deg);
            }
            &.arrow-top {
                top: 10px;
                bottom: auto;
            }
            &.arrow-bottom {
                bottom: 10px;
                top: auto;
                transform: rotate(180deg);
            }
        }
    }

    .ec-slide-option {
        position: absolute;
        font-size: 0;
        bottom: 10px;
        text-align: center;
        width: 100%;
        z-index: 5;
        &.isFirst {
            span:first-child {
                display: none;
            }
        }
        &.isLast {
            span:last-child {
                display: none;
            }
        }
        span {
            border-radius: 100%;
            margin: 0 5px;
            background: #fff;
            display: inline-block;
            width: 10px;
            height: 10px;
            &.active {
                background: #09f;
            }
        }
        &.ec-slide-option-top {
            display: table;
            width: 10px;
            height: 100%;
            top: 0;
            right: 10px;
            margin: auto;
            bottom: 0;
            span {
                margin: 5px 0;
            }
            div {
                display: table-cell;
                vertical-align: middle;
            }
        }
    }
</style> 

运行结果,就是下面这样

clipboard.png

3-4执行动画

布局搞定了,下面就可以写动画,让轮播动起来!这里也需要增加几个变量,一个是nowIndex,记录当前索引。一个是timer定时器!
首先,我用transform:translate3d()这个方式控制ul的滑动。

<ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex))+'%,0,0)','transition-timing-function':slideChange,'transition': 'all .4s'}">
    <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
        <a :href="li.href?li.href:'javascript:;'">
            <img :src="li.src" class="slider-img"/>
        </a>
    </li>
</ul>

然后,根据nowIndex,设置对应点的class。

<div class="switch-option" v-if="option">
    <div>
        <!--如果当前索引index等于nowIndex。则添加active这个class,点就会变成蓝色-->
        <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
    </div>
</div>

js代码如下

<script type="text/javascript">
    export default {
        data () {
            return {
                nowIndex: 0,
                timer: null,
                slideChange: '',
                arrowWidth: '',
                arrowHeight: '',
            }
        },
        computed: {
            //ul宽度
            ulWidth: function () {
                return (this.list.length) + "00%";

            },
            //li宽度
            listWidth: function () {
                return 100 / (this.list.length)
            }
        },
        mounted(){
            //是否自动播放
            if (this.autoplay) {
                this.autoSwitch();
            }
            //设置初始值
            this.slideChange = this.sildetype || 'ease';
            if (this.arrowsize && this.arrowurl) {
                this.arrowWidth = this.arrowsize.split(',')[0];
                this.arrowHeight = this.arrowsize.split(',')[1];
            }
        },
        props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
        methods: {
            //滑动操作
            switchDo(reduce){
                clearInterval(this.timer);
                //根据reduce判断this.nowIndex的增加或者减少!
                //如果是减少模式reduce=‘reduce’
                if (reduce === 'reduce') {
                    //如果nowIndex等于0,已经是第一个了,就回到最后一个
                    if (this.nowIndex === 0) {
                        this.nowIndex = this.list.length - 1;
                    }
                    else {
                        this.nowIndex--;
                    }
                }
                //如果是增加模式reduce=undefined
                else {
                    //如果nowIndex等于this.list.length-1,已经是最后一个了,就回到第一个
                    if (this.nowIndex === this.list.length-1) {
                        this.nowIndex = 0;
                    }
                    else{
                        this.nowIndex++;
                    }
                }
                //如果需要自动播放
                if (this.autoplay) {
                    this.autoSwitch();
                }

            },
            //自动播放函数
            autoSwitch(){
                let time = this.time || 4000;
                this.timer = setInterval(() => {
                    this.switchDo();
                }, time);
            }
        }
    }
</script>    

到了这里,剩下的就只有点击两个箭头,执行相应动画,这个就相对简单,无非就是调用switchDo函数,唯一区别在于,点击左边的箭头,是减少模式,右边箭头的增加模式。代码如下,很好理解。

<!--判断是否需要显示箭头-->
<div class="switch-arrow" v-if="arrowurl&&arrowsize">
    <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}"
         :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}" @click.stop="switchDo('reduce')"></div>
    <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}"
         :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}" @click.stop="switchDo"></div>
</div>

到了这里,对交互有强迫症的开发者就受不了了,到了最后一张,再点击右边箭头,就会出现下面的情况!

clipboard.png

到了第一张,再点击左边箭头也是类似的情况,这样就很不好。理想情况是下面这样

clipboard.png

3-5细节优化

要想做上面的效果,改的地方会比较多,先说下原理吧,到了最后一张,这个时候,再点击右边箭头,像淘宝那样,回到第一张。到了第一张,再点击左边箭头类似效果回到最后一张。那么最后的布局是这样

clipboard.png

这样布局能实现效果,到了最后一张,这个时候,再点击右边箭头,像淘宝那样,回到第一张。就像下面

clipboard.png

这个时候,就需要多做一步,滚动到这里的时候,瞬间拉回去。而且这个拉回去,要把ul的过渡效果transition去掉,不然就会看到拉回去的过渡效果!同时要改变nowIndex。

clipboard.png

1.首先,ul布局方面

<div class="switch-img-type switch-img-left" v-if="type==='slide'&&direction==='left'">
    <!--用tran这个class控制ul是否含有过渡效果,样式已经写好-->
    <ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex+1))+'%,0,0)','transition-timing-function':slideChange}"
        :class="{'tran':noLast}">
        <!--最后一张图片-->
        <li :style="{'width':listWidth+'%'}">
            <a :href="list[list.length-1].href?list[list.length-1].href:'javascript:;'">
                <img :src="list[list.length-1].src" class="slider-img"/>
            </a>
        </li>
        <!--遍历出来的图片-->
        <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
            <a :href="li.href?li.href:'javascript:;'">
                <img :src="li.src" class="slider-img"/>
            </a>
        </li>
        <!--第一张图片-->
        <li :style="{'width':listWidth+'%'}">
            <a :href="list[0].href?list[0].href:'javascript:;'">
                <img :src="list[0].src" class="slider-img"/>
            </a>
        </li>
    </ul>
</div>

2.然后,对应的点修改

<!--isLast:隐藏最后一个span,isFirst隐藏第一个span-->
<div class="switch-option" v-if="option"
     :class="{'isLast':nowIndex===list.length, 'isFirst':nowIndex===-1,'switch-option-top':direction==='top'}">
    <div>
        <span class="active span1" v-if="nowIndex===list.length"></span>
        <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
        <span class="active span2" v-if="nowIndex===-1"></span>
    </div>
</div>

这个可能会有点绕,我解释下,比如滚动最后一张了,再点击右边箭头,向右滑动到第一张的时候,如下图

clipboard.png

这个时候又要把第一个点变成蓝色,但是对应点的索引和nowIndex对不上,这个时候用一个技巧。把第一个(.span1)点显示出来,然后把最后一个点隐藏。这样还是用户看到还是看到4个点在屏幕!等动画执行完了,拉回去第一张的时候。把.span1隐藏,正常显示对应的点!这个大家细想一下就知道了。到了第一张,再点击左边箭头类似效果回到最后一张也是相同的处理方式!

clipboard.png

到这里,功能就基本完成了,下面给出这部分代码!

<template>
    <div class="ec-slide-img-box" id="ec-slide-box">
        <div class="ec-slide-img-type ec-slide-img-left" v-if="type==='slide'&&direction==='left'">
            <!--用tran这个class控制ul是否含有过渡效果,样式已经写好-->
            <ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex+1))+'%,0,0)','transition-timing-function':slideChange}"
                :class="{'tran':noLast}">
                <!--最后一张图片-->
                <li :style="{'width':listWidth+'%'}">
                    <a :href="list[list.length-1].href?list[list.length-1].href:'javascript:;'">
                        <img :src="list[list.length-1].src" class="slider-img"/>
                    </a>
                </li>
                <!--遍历出来的图片-->
                <li v-for="(li,index) in list" :style="{'width':listWidth+'%'}">
                    <a :href="li.href?li.href:'javascript:;'">
                        <img :src="li.src" class="slider-img"/>
                    </a>
                </li>
                <!--第一张图片-->
                <li :style="{'width':listWidth+'%'}">
                    <a :href="list[0].href?list[0].href:'javascript:;'">
                        <img :src="list[0].src" class="slider-img"/>
                    </a>
                </li>
            </ul>
        </div>
        <!--isLast:隐藏最后一个span,isFirst隐藏第一个span-->
        <div class="ec-slide-option" v-if="option"
             :class="{'isLast':nowIndex===list.length, 'isFirst':nowIndex===-1,'ec-slide-option-top':direction==='top'}">
            <div>
                <span class="active" v-if="nowIndex===list.length"></span>
                <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
                <span class="active" v-if="nowIndex===-1"></span>
            </div>
        </div>
        <div class="ec-slide-arrow" v-if="arrowurl&&arrowsize">
            <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}"
                 :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"
                 @click.stop="switchDo('reduce')"></div>
            <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}"
                 :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"
                 @click.stop="switchDo"></div>
        </div>
    </div>
</template>
<script type="text/javascript">
    export default {
        data () {
            return {
                nowIndex: 0,
                noLast: true,
                timer: null,
                slideChange: '',
                arrowWidth: '',
                arrowHeight: ''
            }
        },
        computed: {
            ulWidth: function () {
                return (this.list.length + 2) + "00%";

            },
            listWidth: function () {
                return 100 / (this.list.length + 2)
            }
        },
        mounted(){
            if (this.autoplay) {
                this.autoSwitch();
            }
            this.slideChange = this.sildetype || 'ease';
            if (this.arrowsize && this.arrowurl) {
                this.arrowWidth = this.arrowsize.split(',')[0];
                this.arrowHeight = this.arrowsize.split(',')[1];
            }
        },
        props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl', 'arrowsize', 'option', 'direction'],
        methods: {
            //滑动操作
            switchDo(reduce){
                clearInterval(this.timer);
                //根据reduce判断this.nowIndex的增加或者减少!
                if (reduce === 'reduce') {
                    if (this.nowIndex === 0) {
                        //如果是滑动切换
                        this.nowIndex--;
                        //执行完了这次动画之后,去除过渡效果
                        setTimeout(() => {
                            this.nowIndex = this.list.length - 1;
                            this.noLast = false;
                        }, 400)
                    }
                    else {
                        this.nowIndex--;
                    }
                }
                else {
                    this.nowIndex++;
                }
                if (this.nowIndex === this.list.length) {

                    //执行完了这次动画之后,去除过渡效果
                    setTimeout(() => {
                        this.nowIndex = 0;
                        this.noLast = false;
                    }, 400)

                }
                //如果需要自动播放
                if (this.autoplay) {
                    this.autoSwitch();
                }
                //如果是滑动切换,设置this.noLast,增加过渡效果
                this.noLast = true;

            },
            //自动播放函数
            autoSwitch(){
                let time = this.time || 4000;
                this.timer = setInterval(() => {
                    this.switchDo();
                }, time);
            }
        }
    }
</script>
<style lang="scss">
    .ec-slide-img-box {
        width: 100%;
        height: 100%;
        position: relative;
        touch-action: none;
    }

    .ec-slide-img-type {
        position: relative;
        overflow: hidden;
        width: 100%;
        height: 100%;
        &.ec-slide-img-top {
        }
        &.ec-slide-img-left {
            li {
                display: inline-block;
                font-size: 0;
            }
        }
        &.ec-slide-img-transparent {
            li {
                opacity: 0;
                transition: opacity 1s;
                width: 0;
                &.cur {
                    width: auto;
                }
                &.show {
                    opacity: 1;
                }
            }
        }
        ul {
            font-size: 0;
            &.tran {
                transition: all .4s;
            }
            li {
                text-align: center;
            }

            img {
                vertical-align: middle;
                max-width: 100%;
                max-height: 100%;
            }
        }
    }

    .ec-slide-arrow {
        div {
            position: absolute;
            z-index: 2;
            margin: auto;
            top: 0;
            bottom: 0;
            right: 0;
            left: 0;
            opacity: .5;
            &:hover {
                opacity: 1;
            }
            &.arrow-left {
                left: 10px;
                right: auto;
            }
            &.arrow-right {
                right: 10px;
                left: auto;
                transform: rotate(180deg);
            }
            &.arrow-top {
                top: 10px;
                bottom: auto;
            }
            &.arrow-bottom {
                bottom: 10px;
                top: auto;
                transform: rotate(180deg);
            }
        }
    }

    .ec-slide-option {
        position: absolute;
        font-size: 0;
        bottom: 10px;
        text-align: center;
        width: 100%;
        z-index: 5;
        &.isFirst {
            span:first-child {
                display: none;
            }
        }
        &.isLast {
            span:last-child {
                display: none;
            }
        }
        span {
            border-radius: 100%;
            margin: 0 5px;
            background: #fff;
            display: inline-block;
            width: 10px;
            height: 10px;
            &.active {
                background: #09f;
            }
        }
        &.ec-slide-option-top {
            display: table;
            width: 10px;
            height: 100%;
            top: 0;
            right: 10px;
            margin: auto;
            bottom: 0;
            span {
                margin: 5px 0;
            }
            div {
                display: table-cell;
                vertical-align: middle;
            }
        }
    }
</style> 
  

3-6其它切换方式

码农怎么会满足于现状,只有一种切换方式,怎么行,所以我又完善了些,1.一个透明度的切换方式。2.当传进的list长度为1的时候只显示图片,不进行任何动画。3.左右滑动事件的处理(不规范处理)!虽然也是很少功能,但是我在日常开发可以满足!
完整代码如下,大家也可以去github上面看代码ec-slider

<template>
    <div class="ec-slide-img-box" id="ec-slide-box">
        <!--只有一张图片的时候,只显示,不做任何操作-->
        <div class="ec-slide-img" v-if="list.length===1">
            <a :href="list[0].href?list[0].href:'javascript:;'">
                <img :src="list[0].src"/>
            </a>
        </div>
        <!--左右滑动方式-->
        <div class="ec-slide-img-type ec-slide-img-left" v-if="type==='slide'&&direction==='left'&&list.length>1">
            <!--用tran这个class控制ul是否含有过渡效果,样式已经写好-->
            <ul :style="{'width':ulWidth,'transform':'translate3d(-'+(listWidth*(nowIndex+1))+'%,0,0)','transition-timing-function':slideChange}"
                :class="{'tran':noLast}" @touchstart="touchStar" @touchend="touchEnd">
                <!--最后一张图片-->
                <li :style="{'width':listWidth+'%'}">
                    <a :href="list[list.length-1].href?list[list.length-1].href:'javascript:;'">
                        <img :src="list[list.length-1].src" class="slider-img"/>
                    </a>
                </li>
                <!--遍历出来的图片-->
                <li v-for="(li,index) in list":style="{'width':listWidth+'%'}">
                    <a :href="li.href?li.href:'javascript:;'">
                        <img :src="li.src" class="slider-img"/>
                    </a>
                </li>
                <!--第一张图片-->
                <li :style="{'width':listWidth+'%'}">
                    <a :href="list[0].href?list[0].href:'javascript:;'">
                        <img :src="list[0].src" class="slider-img"/>
                    </a>
                </li>
            </ul>
        </div>
        <!--上下滑动方式-->
        <div class="ec-slide-img-type ec-slide-img-top" v-if="type==='slide'&&direction==='top'&&list.length>1" :style="{'height':boxHeight}">
            <ul :style="{'transform':'translate3d(0,-'+(listWidth*(nowIndex+1))+'%,0)','transition-timing-function':slideChange}"
                :class="{'tran':noLast}" @touchstart="touchStar" @touchend="touchEnd">
                <li>
                    <a :href="list[list.length-1].href?list[list.length-1].href:'javascript:;'">
                        <img :src="list[list.length-1].src" class="slider-img" @load="imgLoad"/>
                    </a>
                </li>
                <li v-for="(li,index) in list">
                    <a :href="li.href?li.href:'javascript:;'">
                        <img :src="li.src" class="slider-img" @load="imgLoad"/>
                    </a>
                </li>
                <li>
                    <a :href="list[0].href?list[0].href:'javascript:;'">
                        <img :src="list[0].src" class="slider-img" @load="imgLoad"/>
                    </a>
                </li>
            </ul>
        </div>
        <!--透明度滑动方式-->
        <div class="ec-slide-img-type ec-slide-img-transparent" v-if="type==='transparent'&&list.length>1">
            <ul @touchstart="touchStar" @touchend="touchEnd">
                <li v-for="(li,index) in list" :class="{'cur':index===nowIndex,'show':index===nowIndexShow}">
                    <a :href="li.href?li.href:'javascript:;'">
                        <img :src="li.src" class="slider-img"/>
                    </a>
                </li>
            </ul>
        </div>
        <!--isLast:隐藏最后一个span,isFirst隐藏第一个span-->
        <div class="ec-slide-option" v-if="option&&list.length>1" :class="{'isLast':nowIndex===list.length, 'isFirst':nowIndex===-1,'ec-slide-option-top':direction==='top'}">
            <div>
                <span class="active span1" v-if="nowIndex===list.length"></span>
                <span v-for="(li,index) in list" :class="{'active':index===nowIndex}"></span>
                <span class="active span2" v-if="nowIndex===-1"></span>
            </div>
        </div>
        <div class="ec-slide-arrow" v-if="arrowurl&&arrowsize&&list.length>1">
            <div :class="{'arrow-left':direction==='left','arrow-top':direction==='top'}" :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}" @click.stop="switchDo('reduce')"></div>
            <div :class="{'arrow-right':direction==='left','arrow-bottom':direction==='top'}" :style="{'width':arrowWidth+'px','height':arrowHeight+'px','background':'url('+arrowurl+') no-repeat','background-size':'100%'}"  @click.stop="switchDo"></div>
        </div>
    </div>
</template>
<script type="text/javascript">
    export default {
        data () {
            return {
                nowIndex: 0,
                nowIndexShow:0,
                noLast: true,
                timer: null,
                slideChange: '',
                arrowWidth:'',
                arrowHeight:'',
                startX:0,
                startY:0,
                boxHeight:0
            }
        },
        computed: {
            ulWidth: function () {
                return (this.list.length + 2) + "00%";

            },
            listWidth:function () {
                return 100/(this.list.length+2)
            }
        },
        mounted(){
            if (this.autoplay) {
                this.autoSwitch();
            }
            this.slideChange = this.sildetype || 'ease';
            if(this.arrowsize&&this.arrowurl){
                this.arrowWidth=this.arrowsize.split(',')[0];
                this.arrowHeight=this.arrowsize.split(',')[1];
            }
        },
        props: ['list', 'autoplay', 'type', 'time', 'sildetype', 'arrowurl','arrowsize','option','direction'],
        methods: {
            //开始滑动
            touchStar(e){
                //e.preventDefault();
                this.startX=e.changedTouches[0].clientX;
                this.startY=e.changedTouches[0].clientY;
            },
            //滑动结束
            touchEnd(e){
                //e.preventDefault();
                if(this.direction==='left'){
                    if(e.changedTouches[0].clientX-this.startX>50){
                        this.switchDo('reduce')
                    }
                    else if(e.changedTouches[0].clientX-this.startX<-50){
                        this.switchDo()
                    }
                }
                else if(this.direction==='top'){
                    if(e.changedTouches[0].clientY-this.startY>50){
                        this.switchDo('reduce')
                    }
                    else if(e.changedTouches[0].clientY-this.startY<-50){
                        this.switchDo()
                    }
                }
            },
            //滑动操作
            switchDo(reduce){
                clearInterval(this.timer);
                //根据reduce判断this.nowIndex的增加或者减少!
                if(reduce==='reduce'){
                    if(this.nowIndex===0){
                        //如果是滑动切换
                        if(this.type==='slide'){
                            this.nowIndex--;
                            //执行完了这次动画之后,去除过渡效果
                            setTimeout(() => {
                                this.nowIndex = this.list.length-1;
                                this.noLast = false;
                            }, 400)
                        }
                        else{
                            this.nowIndex = this.list.length-1;
                        }
                    }
                    else{
                        this.nowIndex--;
                    }
                }
                else{
                    this.nowIndex++;
                }
                if (this.nowIndex === this.list.length) {
                    if(this.type==='slide') {
                        //执行完了这次动画之后,去除过渡效果
                        setTimeout(() => {
                            this.nowIndex = 0;
                            this.noLast = false;
                        }, 400)
                    }
                    else{
                        this.nowIndex = 0;
                    }
                }
                //是否显示图片,只针对透明度切换的情况!
                setTimeout(()=>{
                    this.nowIndexShow=this.nowIndex;
                },1)
                //如果需要自动播放
                if (this.autoplay) {
                    this.autoSwitch();
                }
                //如果是滑动切换,设置this.noLast,增加过渡效果
                if(this.type==='slide') {
                    this.noLast = true;
                }

            },
            //自动播放函数
            autoSwitch(){
                let time = this.time || 4000;
                this.timer = setInterval(() => {
                    this.switchDo();
                }, time);
            },
            //获取最大的高度,针对上下方向,滑动切换方式的处理
            imgLoad(e){
                if(parseInt(this.boxHeight)<e.path[0].offsetHeight){
                    this.boxHeight=e.path[0].offsetHeight+'px';
                }
            }
        }
    }
</script>
<style lang="scss">
    .ec-slide-img-box {
        width: 100%;
        height: 100%;
        position: relative;
        touch-action: none;
    }
    .ec-slide-img{
        width: 100%;
        height: 100%;
        img{
            max-width: 100%;
            max-height: 100%;
        }
    }
    .ec-slide-img-type{
        position: relative;
        overflow: hidden;
        width: 100%;
        height: 100%;
        &.ec-slide-img-top{
        }
        &.ec-slide-img-left{
            li {
                display: inline-block;
                font-size: 0;
            }
        }
        &.ec-slide-img-transparent {
            li {
                opacity: 0;
                transition: opacity 1s;
                width: 0;
                &.cur {
                    width: auto;
                }
                &.show{
                    opacity: 1;
                }
            }
        }
        ul {
            font-size: 0;
            &.tran {
                transition: all .4s;
            }
            li{
                text-align: center;
            }

            img {
                vertical-align: middle;
                max-width: 100%;
                max-height: 100%;
            }
        }
    }
    .ec-slide-arrow {
        div {
            position: absolute;
            z-index: 2;
            margin: auto;
            top: 0;
            bottom: 0;
            right: 0;
            left: 0;
            opacity: .5;
            &:hover{
                opacity: 1;
            }
            &.arrow-left {
                left: 10px;
                right: auto;
            }
            &.arrow-right {
                right: 10px;
                left: auto;
                transform: rotate(180deg);
            }
            &.arrow-top {
                top: 10px;
                bottom: auto;
            }
            &.arrow-bottom {
                bottom: 10px;
                top: auto;
                transform: rotate(180deg);
            }
        }
    }
    .ec-slide-option {
        position: absolute;
        font-size: 0;
        bottom: 10px;
        text-align: center;
        width: 100%;
        z-index: 5;
        &.isFirst {
            span:first-child {
                display: none;
            }
        }
        &.isLast {
            span:last-child {
                display: none;
            }
        }
        span {
            border-radius: 100%;
            margin: 0 5px;
            background: #fff;
            display: inline-block;
            width: 10px;
            height: 10px;
            &.active {
                background: #09f;
            }
        }
        &.ec-slide-option-top{
            display: table;
            width: 10px;
            height: 100%;
            top: 0;
            right: 10px;
            margin: auto;
            bottom: 0;
            span{
                margin:5px 0;
            }
            div{display: table-cell;vertical-align: middle;}
        }
    }
</style>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <title>Title</title>
    <style>
        body{

        }
        .transparent-left{
            margin-bottom: 20px;
            width: 500px;
        }
        .transparent-top{
            max-width: 1000px;
            margin-bottom: 20px;

        }
        .slider-left{
            width: 80%;
            margin: 20px auto;
            max-width: 1000px;
        }
        .slider-top{
            width: 1000px;
            height: 500px;
        }
        .slider-one{
            margin: 20px auto;
            width: 500px;
        }
    </style>
</head>
<body>
<div id="app6">
    <!--http://i2.kiimg.com/1949/098c291e8db16ab5.jpg          http://i1.buimg.com/1949/4d860a3067fab23b.jpg-->
    <div class="transparent-top">
        <ec-slide :list='list' :autoplay="true" :type="'transparent'" :option="true" :time="4000" :sildetype="'ease'" :arrowurl="'http://i2.kiimg.com/1949/098c291e8db16ab5.jpg'" :arrowsize="'40,20'" :direction="'top'"></ec-slide>
    </div>
    <div class="transparent-left">
        <ec-slide :list='list' :autoplay="true" :type="'transparent'" :option="true" :time="4000" :sildetype="'ease'" :arrowurl="'http://i1.buimg.com/1949/4d860a3067fab23b.jpg'" :arrowsize="'20,40'" :direction="'left'"></ec-slide>
    </div>
    <div class="slider-left">
        <ec-slide :list='list' :autoplay="true" :type="'slide'" :option="true" :time="4000" :sildetype="'ease'" :arrowurl="'http://i1.buimg.com/1949/4d860a3067fab23b.jpg'" :arrowsize="'20,40'" :direction="'left'"></ec-slide>
    </div>
    <div class="slider-top">
        <ec-slide :list='list' :autoplay="true" :type="'slide'" :option="true" :time="4000" :sildetype="'ease'" :arrowurl="'http://i2.kiimg.com/1949/098c291e8db16ab5.jpg'" :arrowsize="'40,20'" :direction="'top'"></ec-slide>
    </div>
    <div class="slider-one">
        <ec-slide :list='list2'></ec-slide>
    </div>
</div>
</body>
</html>

4.小结

好了,今天的开发就到此为止了。起初这个项目我是打算当练手用的,但是后来在项目上使用了,虽然这个写得比较简单,但是效果还不错。现在情况还不是很好,以后有需要也会维护。目前来说,也是建议大家可以玩下这个项目,虽然文章有点长,但是直接看下,边动手写代码,边看文章,会发现。一下子就看完了!这个应该是不错的练手项目,可以熟悉使用vue开发组件!最后,如果大家觉得有哪里写错了,写得不好,欢迎指点!

-------------------------华丽的分割线--------------------
想了解更多,关注关注我的微信公众号:守候书阁

clipboard.png


守候
18.6k 声望11.8k 粉丝

一个web前端新手,正在web前端的路上打拼的小伙子。