mySwiper.vue

<template>    <div class="swiper-box" ref="swiperBox">        <!-- 滑块主体 -->        <div class="swiper-wrapper" :style="{width: width + 'rem', height: oHeight}" @touchstart="startMove"             @touchend="endMove" @touchmove="move" ref="sliderBox">            <slot class="swiper-sliber" />        </div>        <!-- 按钮 -->        <div class="btn-box" v-if="nextBtn" @touchstart="startMove" @touchend="endMove" @touchmove="move">            <p @click.stop="handleLast" :class="{'click': isClickL}"><i class="iconfont icon-arrow-left-s-fill"></i></p>            <p @click.stop="handleNext" :class="{'click': isClickR}"><i class="iconfont icon-arrow-right-s-fill"></i>            </p>        </div>        <!-- 分页文字 -->        <div v-if="pagingText !== 'none'" class="paging-text-box"             :class="{left: pagingPosition === 'left', right: pagingPosition === 'right'}">            <span class="paging">{{pagingText !== 'none'? pagingText : ''}}{{loop ? length-2 + '/' + index : length + '/' + (index + 1)}}</span>        </div>        <!-- 分页器 -->        <div class="hint-list-box" v-if="paging !== 'none'">            <ul class="hint-list" :class="{center: paging === 'center', right: paging === 'right' }">                <li class="item" v-for="item in hintLen" :key="item"                    :class="{active: (item === index && loop === true) || (!loop && item === index + 1)}">                </li>            </ul>        </div>    </div></template><script>    export default {        name: 'my-swiper',        data() {            return {                startX: 0,                endX: 0,                nowLeft: 0,                isClickL: false,                isClickR: false,                length: 0,                index: 0,                width: 0,                resistance: 1,                itemWidth: 0,                timer: null,                autoTimer: null,                zoomTimer: null            }        },        props: {            oHeight: { //设置组件高度                type: String,                default: 'auto'            },            nextBtn: { //是否显示切换按钮默认false                type: Boolean,                default: false            },            loop: {     //是否循环播放默认false                type: Boolean,                default: false            },            paging: {   // 是否显示分页器默认none不显示 可选 left, center, right                type: String,                default: 'center'            },            pagingText: { // 是否显示页数 默认none不显示 传入字符串为页数文字前缀                type: String,                default: 'none'            },            pagingPosition: { //页数定位默认center居中 可选left  right                type: String,                default: 'center'            },            defaultIndex: { //默认开始轮播索引 默认0 请填写你传入元素长度内的 数字                type: Number,                default: 0            },            automation: { // 是否主动轮播 为0时不主动轮播  默认3000 每3000ms轮播一次                type: Number,                default: 0            },            zoom: {   // 是否启动zoom模式 要配合子组件宽度一起应用 必须在loop模式下开启                type: Boolean,                default: false            },            retract: {  // 是否缩进显示 要配合子组件宽度一起应用 必须在loop模式下开启                type: Boolean,                default: false            }        },        methods: {            // 手指点击时触发办法, 次要记录手指要开始滑动时x坐标用于计算滑动完结与开始的差值, 触控时革除 主动轮播与轮播动画的定时器            startMove(e) {                clearInterval(this.timer)                       // 革除轮播动画定时器                clearInterval(this.autoTimer)                   // 革除主动轮播定时器                this.startX = e.targetTouches[0].clientX        // 获取触控时x坐标                this.nowLeft = this.$refs.sliderBox.offsetLeft  // 记录以后滑块left值            },            // 手指拖动时触发的,滑动动画            slider(nowX) {                if (this.$refs.sliderBox.offsetLeft > 0 && !this.loop) { // 不是循环模式下 在第一个元素向右拖拽时速度衰减                    this.resistance += 0.03                    window.console.log(55555)                    this.$refs.sliderBox.style.left = (nowX - this.startX) / this.resistance + 'px'                    return                } else if (this.$refs.sliderBox.offsetLeft < -this.maxLeft && !this.loop) { // 不是循环模式下 在最初一个元素向左拖拽时速度衰减                    this.resistance += 0.03                    this.$refs.sliderBox.style.left = this.nowLeft + (nowX - this.startX) / this.resistance + 'px'                    return                }                this.$refs.sliderBox.style.left = this.nowLeft + (nowX - this.startX) + 'px'            },            // 依据子元素计算 滑块宽度            getItemDom() {                let itemArr = Array.prototype.slice.call(this.$refs.sliderBox.children, 0)                this.length = this.$refs.sliderBox.children.length                itemArr.forEach((ele) => {                    this.width += parseFloat(ele.style.width)                })            },            // zoom模式下滑动动画            zoomMove(nowX) {                if (nowX - this.startX < 0) {                    let scale = (-(nowX - this.startX) * 0.001) + 0.8 > 1 ? 1 : (-(nowX - this.startX) * 0.001) + 0.8                    let opacity = (-(nowX - this.startX) * 0.001) + 0.5 > 1 ? 1 : (-(nowX - this.startX) * 0.0015) + 0.5                    this.$refs.sliderBox.children[this.index + 1].style.transform = 'scale(' + scale + ')'                    this.$refs.sliderBox.children[this.index + 1].style.opacity = opacity                } else {                    let scale = ((nowX - this.startX) * 0.001) + 0.8 > 1 ? 1 : ((nowX - this.startX) * 0.001) + 0.8                    let opacity = ((nowX - this.startX) * 0.001) + 0.5 > 1 ? 1 : ((nowX - this.startX) * 0.0015) + 0.5                    this.$refs.sliderBox.children[this.index - 1].style.transform = 'scale(' + scale + ')'                    this.$refs.sliderBox.children[this.index - 1].style.opacity = opacity                }            },            // 手指拖动时触发该办法            move(e) {                let nowX = e.targetTouches[0].clientX                this.slider(nowX)                           // 调用滑动动画函数                this.zoom && this.zoomMove(nowX)            // 调用zoom动画函数            },            // 拖动完结时触发 该办法            endMove(e) {                this.endX = e.changedTouches[0].clientX                if (this.resistance !== 1) this.resistance = 1                // 判断拖动间隔 是否扭转index                if (this.distance > 50) {                    this.handleLast('touch')                } else if (this.distance < -50) {                    this.handleNext('touch')                } else {                    this.animation(this.$refs.sliderBox, this.target)                }                // 在主动轮播时 拖动完结重新启动定时器                this.autoTimer = this.automation ? this.handleAuto() : null            },            // 切换下一个函数, 次要以扭转index形式切换            handleLast(isTouch) {                this.isClickL = true                if (!this.loop) {                    if (!this.index && isTouch !== 'touch') {                        this.index = this.length - 1                    } else if (this.index) {                        this.index--                    } else {                        this.animation(this.$refs.sliderBox, this.target)                    }                } else {                    if (this.index === 1) {                        this.transposition()                        this.index = this.length - 2                    } else {                        this.index--                    }                }                this.zoom && this.$refs.sliderBox && this.zoomAnimation(this.$refs.sliderBox.children[this.index])                setTimeout(() => {                    this.isClickL = false                }, 300)            },            // 切换上一个函数, 次要以扭转index形式切换            handleNext(isTouch) {                this.isClickR = true                if (!this.loop) {                    if (this.index === this.length - 1 && isTouch !== 'touch') {                        this.index = 0                    } else if (this.index !== this.length - 1) {                        this.index++                    } else {                        this.animation(this.$refs.sliderBox, this.target)                    }                } else {                    if (this.index === this.length - 2) {                        this.transposition('isLast')                        this.index = 1                    } else {                        this.index++                    }                }                this.zoom && this.$refs.sliderBox && this.zoomAnimation(this.$refs.sliderBox.children[this.index])                setTimeout(() => {                    this.isClickR = false                }, 300)            },            // 切换动画            animation(dom, target) {                clearInterval(this.timer)                let origin = null                let speed = null                this.timer = setInterval(() => {                    origin = dom.offsetLeft                    speed = (target - origin) / 9                    speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed)                    if (Math.abs(target - dom.offsetLeft) < 2) {                        dom.style.left = target + 'px'                        clearInterval(this.timer)                        return                    }                    dom.style.left = origin + speed + 'px'                }, 16)                this.endX = 0                this.startX = 0                if (this.zoom) {                    this.isZoomActive()                }            },            // zoom动画            zoomAnimation(dom) {                clearInterval(this.zoomTimer)                let originScale = Number(this.getDomTransform(dom, 'scale'))                let originOpacity = Number(dom.style.opacity)                if (originScale === 1 && originOpacity === 1) {                    clearInterval(this.zoomTimer)                    return                }                let speed = 0.02                this.zoomTimer = setInterval(() => {                    if (originScale >= 1 && originOpacity >= 1) {                        clearInterval(this.zoomTimer)                        return                    }                    originScale += speed                    originOpacity += speed                    dom.style.transform = originScale > 1 ? `scale(1)` : `scale(${originScale})`                    dom.style.opacity = originOpacity                }, 16)            },            // 获取元素transform函数            getDomTransform(dom, type) {                let transformStr = dom.style.transform                let tarnArr = transformStr.split(' ')                let obj = {}                tarnArr.forEach(item => {                    let tempArr = item.split('(')                    obj[tempArr[0]] = tempArr[1].slice(0, -1)                })                return type ? obj[type] : obj            },            // loop模式的后期渲染函数, 感觉这里写的不是很漂亮要害是想把,滑块中的每一项以标签模式传进来,没想到更好的办法,如果是数组的模式就要好些            renderLoop() {                if (this.loop) {                    let divArr = Array.prototype.slice.call(this.$refs.sliderBox.children, 0)                    let len = divArr.length                    let deviceWidth = document.documentElement.clientWidth                    let first = divArr[0].cloneNode(true)                    let last = divArr[len - 1].cloneNode(true)                    this.$refs.sliderBox.appendChild(first)                    this.$refs.sliderBox.insertBefore(last, this.$refs.sliderBox.children[0])                    setTimeout(() => {                        this.$refs.sliderBox.style.left = -(this.defaultIndex + 1) * this.$refs.sliderBox.children[0].offsetWidth + (deviceWidth - this.$refs.sliderBox.children[0].offsetWidth)/2 + 'px'                        window.console.log('-----', this.$refs.sliderBox.style.left)                        if (this.defaultIndex <= this.length - 1) {                            this.index = this.defaultIndex + 1                        }                    })                } else {                    setTimeout(() => {                        this.$refs.sliderBox.style.left = -this.defaultIndex * this.$refs.sliderBox.children[0].offsetWidth + 'px'                        this.index = this.defaultIndex                    })                }                this.getItemDom()            },            // zoom模式中款式调整            isZoomActive() {                if (this.$refs.sliderBox) {                    for (let i = 0; i < this.$refs.sliderBox.children.length; i++) {                        if (i !== this.index) {                            this.$refs.sliderBox.children[i].style.transform = 'scale(0.8)'                            this.$refs.sliderBox.children[i].style.opacity = '0.5'                        } else {                            this.$refs.sliderBox.children[i].style.transform = 'scale(1)'                            this.$refs.sliderBox.children[i].style.opacity = '1'                        }                    }                }            },            // 循环模式中的换位函数            transposition(isLast) {                if (isLast) {                    if (this.$refs.sliderBox) this.$refs.sliderBox.style.left = this.distance + 'px'                    return                }                this.$refs.sliderBox.style.left = -(this.length - 1) * this.$refs.sliderBox.children[0].offsetWidth + this.distance + 'px'            },            // 启动主动模式            handleAuto() {                if (this.automation) {                    return (setInterval(() => {                        this.handleNext()                    }, this.automation))                }            }        },        computed: {            // 手指触碰与来到的差值            distance() {                return this.endX - this.startX            },            // 计算滑块目的地            target() {                if (this.swiperWidth - this.$refs.sliderBox.children[0].offsetWidth !== 0 && this.retract) {                    return -this.index * this.$refs.sliderBox.children[0].offsetWidth + (this.swiperWidth - this.$refs.sliderBox.children[0].offsetWidth) / 2                }                return -this.index * this.$refs.sliderBox.children[0].offsetWidth            },            // 分页个数            hintLen() {                return this.loop && this.length ? this.length - 2 : this.length            },            // box宽度            swiperWidth() {                return this.$refs.swiperBox && this.$refs.swiperBox.offsetWidth            },            // 滑块极限值            maxLeft() {                return this.$refs.sliderBox && this.$refs.sliderBox.offsetWidth - this.$refs.sliderBox.children[0].offsetWidth            }        },        watch: {            // 监控index变动            index() {                this.animation(this.$refs.sliderBox, this.target)            }        },        mounted() {            this.renderLoop()            this.autoTimer = this.automation ? this.handleAuto() : null        },        beforeDestroy() {            clearInterval(this.timer)            clearInterval(this.autoTimer)            clearInterval(this.zoomTimer)        }    }</script><style lang="scss" scoped>    .swiper-box {        width: 100%;        overflow: hidden;        position: relative;        .swiper-wrapper {            position: relative;            overflow: hidden;            display: flex;        }        .btn-box {            position: absolute;            width: 100%;            top: 50%;            display: flex;            justify-content: space-between;            transform: translateY(-50%);            overflow: hidden;            p {                overflow: hidden;                color: rgba(4, 4, 4, .4);                background-color: rgba(8, 8, 8, .1);                &.click {                    background-color: rgba(8, 8, 8, .6);                    .iconfont {                        color: rgba(4, 4, 4, .6);                    }                }                .iconfont {                    font-size: .8rem;                }            }        }        .hint-list-box {            position: absolute;            width: 100%;            bottom: .2rem;            .hint-list {                display: flex;                &.center {                    justify-content: center;                }                &.right {                    justify-content: flex-end;                }            }            .item {                width: .2rem;                height: .2rem;                border-radius: 50%;                background-color: #eeeeec;                margin-left: .2rem;                &.active {                    background: linear-gradient(#6a3, #4e6);                }            }        }        .paging-text-box {            position: absolute;            top: .1rem;            text-align: center;            width: 100%;            &.right {                text-align: right;                span {                    margin-right: 1rem;                }            }            &.left {                text-align: left;                span {                    margin-left: 1rem;                }            }        }    }</style>

mySwiperItem.vue

<!-- * @Author: yang * @Date: 2020-11-09 07:04:39 * @LastEditors: yang * @LastEditTime: 2020-11-09 10:45:21 * @FilePath: \gloud-h5\src\component\index\mySwiperItem.vue--><template>    <div :style="{height: height + 'rem', width: width + 'rem'}" class="item-wrapper" >        <slot :ref="'item' + name"></slot>    </div></template><script>    export default {        name: "my-swiper-item",        props: {            width: {                type: Number,                required: true            },            height: {                type: String,                default: '4'            }        }    }</script><style scoped></style>

应用的是css预编译less

npm install --save-dev less less-loader css-loader style-loader

应用
index.vue

<template>    <div class="show-swiper-wrapper">        <p class="show-toast-title">轮播图展现</p>        <div class="item">            <divider>根底调用</divider>            <my-swiper>                <my-swiper-item :width="7.5" v-for="item in list" :key="item.id" >                    <img :src="item.img" alt="">                </my-swiper-item>            </my-swiper>        </div>        <div class="item">            <divider>主动轮播</divider>            <my-swiper :automation="2000" paging="right">                <my-swiper-item :width="7.5" v-for="item in list" :key="item.id">                    <img :src="item.img" alt="">                </my-swiper-item>            </my-swiper>        </div>        <div class="item">            <divider>循环有按钮</divider>            <my-swiper :nextBtn="true" :automation="2000" :loop="true" paging="left">                <my-swiper-item :width="7.5" v-for="item in list" :key="item.id">                    <img :src="item.img" alt="">                </my-swiper-item>            </my-swiper>        </div>        <div class="item">            <divider>缩进模式</divider>            <my-swiper :zoom="true" :retract="true" :automation="3000" :oHeight="'3rem'" eight="3" :loop="true" paging="none">                <my-swiper-item :width="5" height="3"  v-for="item in list" :key="item.id">                    <img :src="item.img" alt="">                </my-swiper-item>            </my-swiper>        </div>            <divider>查看源码</divider>            <my-button class="button-item" @button-click="pushLink" text="查看demo源码" size="lang" />    </div></template><script>    import MySwiper from './components/slider/mySwiper'    import MySwiperItem from './components/slider/mySwiperItem'    import Divider from './components/divider'    import MyButton from './components/myButton'    export default {        name: 'showSwiper',        components: {            MySwiper,            MySwiperItem,            Divider,            MyButton        },        data() {            return {                // 拿到本地试验要换门路偶                list: [                    {                        img: require('./static/1.jpg'),                        id: 1                    },                    {                        img: require('./static/2.jpg'),                        id: 2                    },                    {                        img: require('./static/3.jpg'),                        id: 3                    },                    {                        img: require('./static/4.jpg'),                        id: 4                    }                ]            }        },        methods: {            pushLink() {                this.$router.push(`/soundCodeShow${this.$route.fullPath}`)            }        }    }</script><style scoped lang="less">    .show-swiper-wrapper {        display: flex;        flex-direction: column;        align-items: center;        position: relative;        padding-bottom: 1rem;    }    .show-swiper-wrapper .item {        width: 100%;    }    .show-swiper-wrapper .item img {        width: 100%;        height: 100%;    }</style>

成果: