Vue封装 swiper 组件多种方案实现

Vue封装 swiper 组件多种方案实现

第一种方案(原生实现)

不需要引入任何插件


  <template>
  <div class='home'>
    <div v-if="swiperList.length" class="slider-wrapper" ref="sliderWrapper">
      <!-- 第一种方案(原生) -->
      <h3>第一种方案(原生)</h3>
      <protogenesis-swiper ref="swiper">
        <protogenesis-swiper-item v-for="item in swiperList" :key="item.id">
          <a :href="item.linkUrl">
            <img :src="item.imgUrl" alt="">
          </a>
        </protogenesis-swiper-item>
      </protogenesis-swiper>
    </div>
  </div>
</template>

<script>
// 第一种方案(原生)
import {
  ProtogenesisSwiper,
  ProtogenesisSwiperItem
} from "../../components/protogenesisSwiper";

export default {
  name: "Home",
  data() {
    return {
      swiperList: [
        {
          imgUrl: require("../../assets/img/swiper_1.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "1"
        },
        {
          imgUrl: require("../../assets/img/swiper_2.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "2"
        },
        {
          imgUrl: require("../../assets/img/swiper_3.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "3"
        },
        {
          imgUrl: require("../../assets/img/swiper_4.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "4"
        },
        {
          imgUrl: require("../../assets/img/swiper_5.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "5"
        }
      ]
    };
  },
  components: {
    ProtogenesisSwiper,
    ProtogenesisSwiperItem
  },
  methods: {
    // 启动轮播图(原生实现)
    startTimer() {
      this.$refs.swiper &&
        this.$refs.swiper.startTimer &&
        this.$refs.swiper.startTimer();
    },
    // 停止轮播图(原生实现)
    stopTimer() {
      this.$refs.swiper &&
        this.$refs.swiper.stopTimer &&
        this.$refs.swiper.stopTimer();
    }
  },
  destroyed() {
    // 停止轮播图(原生实现)
    this.stopTimer();
  }
};
</script>

<style lang="stylus" scoped>
</style>

ProtogenesisSwiper.vue

<template>
  <div id="hy-swiper">
    <div class="swiper" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
      <slot></slot>
    </div>
    <slot name="indicator"></slot>
    <div class="indicator">
      <slot name="indicator" v-if="showIndicator && slideCount>1">
        <div
          v-for="(item, index) in slideCount"
          class="indi-item"
          :class="{active: index === currentIndex-1}"
          :key="index"
        ></div>
      </slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "ProtogenesisSwiper",
  props: {
    // interval: 每次定时器的间隔时间
    interval: {
      type: Number,
      // default: 默认的内容
      default: 3000
    },
    // animDuration: 每一张图片切换的时间
    animDuration: {
      type: Number,
      default: 300
    },
    // moveRation: 手指移动对应的距离切换到下一张
    moveRatio: {
      type: Number,
      default: 0.25
    },
    // showIndicator: 是否开启指示器
    showIndicator: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      slideCount: 0, // 元素个数
      totalWidth: 0, // swiper的宽度
      swiperStyle: {}, // swiper样式
      currentIndex: 1, // 当前的index
      scrolling: false // 是否正在滚动
    };
  },
  mounted() {
    // 1.操作DOM, 在前后添加 Slider
    setTimeout(() => {
      this.handleDom();

      // 2.开启定时器
      this.startTimer();
    }, 100);
  },
  methods: {
    /**
     * 开启定时器
     */
    startTimer() {
      this.playTimer = window.setInterval(() => {
        this.currentIndex++;
        this.scrollContent(-this.currentIndex * this.totalWidth);
      }, this.interval);
    },
    /**
     * 停止定时器
     */
    stopTimer() {
      window.clearInterval(this.playTimer);
    },
    /**
     * 滚动到正确位置
     */
    scrollContent(currentPosition) {
      // 1.设置滚动开始
      this.scrolling = true;

      // 2.开始滚动动画
      this.swiperStyle.transition = "transform" + this.aninDuration + "ms";
      this.setTransform(currentPosition);

      // 3.判断滚动的位置
      this.checkPosition();

      // 4.滚动完成
      this.scrolling = false;
    },

    /**
     * 校验正确的位置
     */
    checkPosition() {
      window.setTimeout(() => {
        // 1.校验正确的位置
        this.swiperStyle.transition = "0ms";
        // 判断临界点
        if (this.currentIndex >= this.slideCount + 1) {
          this.currentIndex = 1;
          this.setTransform(-this.currentIndex * this.totalWidth);
          // 判断当前的索引值是否小于或者等于 0
        } else if (this.currentIndex <= 0) {
          this.currentIndex = this.slideCount;
          this.setTransform(-this.currentIndex * this.totalWidth);
        }

        // 2.结束移动后的回调
        this.$emit("transitionEnd", this.currentIndex - 1);
      }, this.animDuration);
    },

    /**
     * 设置滚动的位置
     */
    setTransform: function(position) {
      this.swiperStyle.transform = `translate3d(${position}px, 0, 0)`;
      this.swiperStyle[
        "-webkit-transform"
      ] = `translate3d(${position}px), 0, 0`;
      this.swiperStyle["-ms-transform"] = `translate3d(${position}px), 0, 0`;
    },

    /**
     * 操作DOM, 在DOM前后添加Slide
     */
    handleDom: function() {
      // 1.获取要操作的元素
      let swiperEl = document.querySelector(".swiper");
      let slidesEls = swiperEl.getElementsByClassName("slide");

      // 2.保存个数
      this.slideCount = slidesEls.length;

      // 3.如果大于1个, 那么在前后分别添加一个slide
      if (this.slideCount > 1) {
        let cloneFirst = slidesEls[0].cloneNode(true);
        let cloneLast = slidesEls[this.slideCount - 1].cloneNode(true);
        // 添加标签插入到相对应的位置
        swiperEl.insertBefore(cloneLast, slidesEls[0]);
        swiperEl.appendChild(cloneFirst);
        // 设置 totalWidth 来接收 swiperEl的可以区域的宽度
        this.totalWidth = swiperEl.offsetWidth;
        // 设置 swiperStyle 来接收 swiperEl 的属性
        this.swiperStyle = swiperEl.style;
      }

      // 4.让swiper元素, 显示第一个(目前是显示前面添加的最后一个元素)
      this.setTransform(-this.totalWidth);
    },

    /**
     * 拖动事件的处理
     */
    touchStart: function(e) {
      // 1.如果正在滚动, 不可以拖动
      if (this.scrolling) return;

      // 2.停止定时器
      this.stopTimer();

      // 3.保存开始滚动的位置
      this.startX = e.touches[0].pageX;
    },

    /**
     * 手指移动事件
     */
    touchMove: function(e) {
      // 1.计算出用户拖动的距离
      this.currentX = e.touches[0].pageX;
      this.distance = this.currentX - this.startX;
      let currentPosition = -this.currentIndex * this.totalWidth;
      let moveDistance = this.distance + currentPosition;

      // 2.设置当前的位置
      this.setTransform(moveDistance);
    },

    /**
     * 手指抬起事件
     */
    touchEnd: function(e) {
      // 1.获取移动的距离
      let currentMove = Math.abs(this.distance);

      // 2.判断最终的距离
      if (this.distance === 0) {
        return;
      } else if (
        this.distance > 0 &&
        currentMove > this.totalWidth * this.moveRatio
      ) {
        // 右边移动超过0.5
        this.currentIndex--;
      } else if (
        this.distance < 0 &&
        currentMove > this.totalWidth * this.moveRatio
      ) {
        // 向左移动超过0.5
        this.currentIndex++;
      }

      // 3.移动到正确的位置
      this.scrollContent(-this.currentIndex * this.totalWidth);

      // 4.移动完成后重新开启定时器
      this.startTimer();
    },

    /**
     * 控制上一个, 下一个
     */
    previous: function() {
      this.changeItem(-1);
    },

    next: function() {
      this.changeItem(1);
    },

    changeItem: function(num) {
      // 1.移除定时器
      this.stopTimer();

      // 2.修改index和位置
      this.currentIndex += num;
      this.scrollContent(-this.currentIndex * this.totalWidth);

      // 3.添加定时器
      this.startTimer();
    }
  }
};
</script>

<style lang="stylus" scoped>
#hy-swiper
  overflow hidden
  position relative
.swiper
  display flex
.indicator
  display flex
  justify-content center
  position absolute
  width 100%
  bottom 8px
.indi-item
  box-sizing border-box
  width 8px
  height 8px
  border-radius 4px
  background-color #fff
  line-height 8px
  text-align center
  font-size 12px
  margin 0 5px
.indi-item.active
  width 20px
  border-radius 5px
  background-color rgba(212, 62, 46, 1)
</style>

ProtogenesisSwiperItem.vue
 
 <template>
  <div class="slide">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "Slide"
};
</script>

<style lang="stylus" scoped>
.slide
  width 100%
  flex-shrink 0
.slide img
  width 100%
</style>

index.js

import ProtogenesisSwiper from './ProtogenesisSwiper.vue'
import ProtogenesisSwiperItem from './ProtogenesisSwiperItem.vue'

export {
  ProtogenesisSwiper,
  ProtogenesisSwiperItem
}

编译出来的内容

在这里插入图片描述

第二种方案(better-scroll实现)

Home.vue
安装 better-scroll 插件
    npm install better-scroll --save  ||  yarn add better-scroll
<template>
  <div class='home'>
    <div v-if="swiperList.length" class="slider-wrapper" ref="sliderWrapper">
      <!-- 第二种方案(better-scroll) -->
      <h3 style="margin-top: 30px;">第二种方案(better-scroll)</h3>
      <slider>
        <div v-for="item in swiperList" :key="item.id">
          <a :href="item.linkUrl">
            <img :src="item.imgUrl" alt="">
          </a>
        </div>
      </slider>
    </div>
  </div>
</template>

<script>
// 第二种方案(better-scroll实现)
import Slider from "../../components/slider/Slider";

export default {
  name: "Home",
  data() {
    return {
      swiperList: [
        {
          imgUrl: require("../../assets/img/swiper_1.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "1"
        },
        {
          imgUrl: require("../../assets/img/swiper_2.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "2"
        },
        {
          imgUrl: require("../../assets/img/swiper_3.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "3"
        },
        {
          imgUrl: require("../../assets/img/swiper_4.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "4"
        },
        {
          imgUrl: require("../../assets/img/swiper_5.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "5"
        }
      ]
    };
  },
  components: {
    Slider,
  }
};
</script>

<style lang="stylus" scoped>
.home
  position fixed
  width 100%
  top 0
  bottom 0
</style>

Slider.vue
<template>
  <div class="slider" ref="slider">
    <div class="slider-group" ref="sliderGroup">
      <slot></slot>
    </div>
    <div class="dots">
      <span
        class="dot"
        v-for="(item, index) in dots"
        :key="index"
        :class="{active: currentPageIndex === index}"
      ></span>
    </div>
  </div>
</template>

<script>
import BScroll from "better-scroll";
import { addClass } from "./dom.js";
export default {
  name: "Slider",
  props: {
    loop: {
      // 是否无缝循环轮播
      type: Boolean,
      default: true
    },
    autoPlay: {
      // 是否自动轮博
      type: Boolean,
      default: true
    },
    interval: {
      // 轮播的毫秒数
      type: Number,
      default: 1000
    }
  },
  data() {
    return {
      dots: [],
      currentPageIndex: 0 // 控制器默认的下标
    };
  },
  mounted() {
    setTimeout(() => {
      // 设置 slider 的宽度
      this._setSliderWidth();
      // 初始化控制器
      this._initDots();
      // 初始化 slider
      this._initSlider();
      // 判断是否开启了循环轮播
      if (this.autoPlay) {
        this._play();
      }
    }, 20);
  },
  destroyed() {
    clearTimeout(this.timer);
  },
  methods: {
     // 设置 slider 的宽度
    _setSliderWidth() {
      this.children = this.$refs.sliderGroup.children;
      let width = 0;
      let sliderWidth = this.$refs.slider.clientWidth;
      for (let i = 0; i < this.children.length; i++) {
        let child = this.children[i];
        addClass(child, "slider-item");
        child.style.width = sliderWidth + "px";
        width += sliderWidth;
      }
      if (this.loop) { // 当设置无缝循环轮播的时候, 默认加两张图片的宽度方便滚动
        width += 2 * sliderWidth;
      }
      // 设置轮播图的总宽度
      this.$refs.sliderGroup.style.width = width + "px";
    },
    // 初始化 slider
    _initSlider() {
      this.slider = new BScroll(this.$refs.slider, {
        scrollX: true, // 滚动方向为 X 轴
        scrollY: false, // 滚动方向为 Y 轴
        momentum: false, // 当快速滑动时是否开启滑动惯性
        snap: {
          loop: this.loop, // 是否可以无缝循环轮播
          threshold: 0.3, // 用手指滑动时页面可切换的阈值,大于这个阈值可以滑动的下一页
          speed: 400 // 轮播图切换的动画时间
        }, // 该属性是给 slider 组件使用的,普通的列表滚动不需要配置
        click: true // 是否派发click事件,通常判断浏览器派发的click还是betterscroll派发的click,可以用_constructed,若是bs派发的则为true
      });
      this.slider.on("scrollEnd", () => {
        // 滚动结束
        let pageIndex = this.slider.getCurrentPage().pageX;
        this.currentPageIndex = pageIndex;

        if (this.autoPlay) {
          this._play();
        }
      });
       // 滚动开始之前
      this.slider.on("beforeScrollStart", () => {
        if (this.autoPlay) {
          clearTimeout(this.timer);
        }
      });
    },
    // 初始化控制器
    _initDots() {
      this.dots = new Array(this.children.length);
    },
    // 开始轮播
    _play() {
      let pageIndex = this.currentPageIndex + 1;
      this.timer = setTimeout(() => {
        this.slider.next();
      }, this.interval);
    }
  }
};
</script>

<style lang="stylus" scoped>
.slider
  min-height 1px
.slider-group
  position relative
  overflow hidden
  white-space nowrap
.slider-item
  float left
  box-sizing border-box
  overflow hidden
  text-align center
  a
    display block
    width 100%
    overflow hidden
    text-decoration none
  img
    width 100%
    display block
.dots
  position absolute
  right 0
  left 0
  bottom 12px
  text-align center
  font-size 0
.dot
  display inline-block
  margin 0 4px
  width 8px
  height 8px
  border-radius 50%
  background #fff
  &.active
    width 20px
    border-radius 5px
    background red
</style>

dom.js

export function addClass(el, className){
  if(hasClass(el, className)){ // 判断需要添加的类名是否存在
    return
  }

  // 先将变量转换成数组
  let newClass = el.className.split(' ');
  // 然后将类名添加到数组中
  newClass.push(className);
  // 将数组里面的值转换成字符串]
  el.className = newClass.join(' ');
}

export function hasClass(el, className){
  let reg = new RegExp('(^|\\s)' + className + '(\\s|$)');
  return reg.test(el.className);
}

编译内容

在这里插入图片描述

第三种方案(vue-awesome-swiper实现){swiper4}
安装 vue-awesome-swiper 插件
    npm install vue-awesome-swiper --save  ||  yarn add vue-awesome-swiper 

<template>
 <div class='home'>
   <div v-if="swiperList.length" class="slider-wrapper" ref="sliderWrapper">
     <!-- 第三种方案(vue-awesome-swiper) -->
     <h3 style="margin-top: 30px;">第三种方案(vue-awesome-swiper)</h3>
     <swiper :list="swiperList"></swiper>
   </div>
 </div>
</template>

<script>
// 第三种方案(vue-awesome-swiper)
import Swiper from '../../components/swiper/Swiper'

export default {
 name: "Home",
 data() {
   return {
     swiperList: [
       {
         imgUrl: require("../../assets/img/swiper_1.jpg"), // 图片路径
         linkUrl: "", // 跳转路径
         id: "1"
       },
       {
         imgUrl: require("../../assets/img/swiper_2.jpg"), // 图片路径
         linkUrl: "", // 跳转路径
         id: "2"
       },
       {
         imgUrl: require("../../assets/img/swiper_3.jpg"), // 图片路径
         linkUrl: "", // 跳转路径
         id: "3"
       },
       {
         imgUrl: require("../../assets/img/swiper_4.jpg"), // 图片路径
         linkUrl: "", // 跳转路径
         id: "4"
       },
       {
         imgUrl: require("../../assets/img/swiper_5.jpg"), // 图片路径
         linkUrl: "", // 跳转路径
         id: "5"
       }
     ]
   };
 },
 components: {
   Swiper,
 },
};
</script>

<style lang="stylus" scoped>
.home
 position fixed
 width 100%
 top 0
 bottom 0
.slider-wrapper
 position relative
</style>

Swiper.vue

<template>
  <div>
    <div class="wrapper">
      <swiper :options="swiperOption">
        <!-- slides -->
        <swiper-slide v-for="(img, index) of list" :key="index" ref="mySwiper">
          <img class="swiper-img" :src="img.imgUrl" alt="img.imgUrl">
        </swiper-slide>
        <div class="swiper-pagination" slot="pagination"></div>
      </swiper>
    </div>
  </div>
</template>

<script>
export default {
  name: "HomeSwiper",
  props: {
    list: {
      type: Array,
      default: []
    }
  },
  data() {
    return {
      swiperOption: {
        autoplay: {
          disableOnInteraction: false, //手动滑动后可以自动滑动
          delay: 2500
        },
        loop: true, // 无缝轮播
        pagination: {
          el: ".swiper-pagination"
        }
      }
    };
  },
  computed: {}
};
</script>

<style lang="stylus" scoped>
/* 设置分页器未选中的样式 */
.wrapper >>> .swiper-pagination-bullet
  box-sizing border-box
  width 8px
  height 8px
  border-radius 4px
  background-color #fff
  line-height 8px
  text-align center
  font-size 12px
  margin 0 5px
/* 设置分页器选中的样式 */
.wrapper >>> .swiper-pagination-bullet-active
  width 20px
  border-radius 5px
  background-color rgba(212, 62, 46, 1)
.wrapper
  width 100%
  overflow hidden
  background #eee
  .swiper-img
    width 100%
</style>

main.js

import Vue from 'vue'
import App from './App'
// 轮播图插件(vue-awesome-swiper)
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
Vue.use(VueAwesomeSwiper)
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  render: h => h(App)
})

编译内容

在这里插入图片描述

将三种方案都用在Home.vue里面
<template>
  <div class='home'>
    <div v-if="swiperList.length" class="slider-wrapper" ref="sliderWrapper">
      <!-- 第一种方案(原生) -->
      <h3>第一种方案(原生)</h3>
      <protogenesis-swiper ref="swiper">
        <protogenesis-swiper-item v-for="item in swiperList" :key="item.id">
          <a :href="item.linkUrl">
            <img :src="item.imgUrl" alt="">
          </a>
        </protogenesis-swiper-item>
      </protogenesis-swiper>

      <!-- 第二种方案(better-scroll) -->
      <h3 style="margin-top: 30px;">第二种方案(better-scroll)</h3>
      <slider>
        <div v-for="item in swiperList" :key="item.id">
          <a :href="item.linkUrl">
            <img :src="item.imgUrl" alt="">
          </a>
        </div>
      </slider>

      <!-- 第三种方案(vue-awesome-swiper) -->
      <h3 style="margin-top: 30px;">第三种方案(vue-awesome-swiper)</h3>
      <swiper :list="swiperList"></swiper>

    </div>
  </div>
</template>

<script>
// 第一种方案(原生)
import {
  ProtogenesisSwiper,
  ProtogenesisSwiperItem
} from "../../components/protogenesisSwiper";
// 第二种方案(better-scroll实现)
import Slider from "../../components/slider/Slider";
// 第三种方案(vue-awesome-swiper)
import Swiper from '../../components/swiper/Swiper'

export default {
  name: "Home",
  data() {
    return {
      swiperList: [
        {
          imgUrl: require("../../assets/img/swiper_1.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "1"
        },
        {
          imgUrl: require("../../assets/img/swiper_2.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "2"
        },
        {
          imgUrl: require("../../assets/img/swiper_3.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "3"
        },
        {
          imgUrl: require("../../assets/img/swiper_4.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "4"
        },
        {
          imgUrl: require("../../assets/img/swiper_5.jpg"), // 图片路径
          linkUrl: "", // 跳转路径
          id: "5"
        }
      ]
    };
  },
  components: {
    ProtogenesisSwiper,
    ProtogenesisSwiperItem,
    Slider,
    Swiper,
  },
  methods: {
    // 启动轮播图(原生实现)
    startTimer() {
      this.$refs.swiper &&
        this.$refs.swiper.startTimer &&
        this.$refs.swiper.startTimer();
    },
    // 停止轮播图(原生实现)
    stopTimer() {
      this.$refs.swiper &&
        this.$refs.swiper.stopTimer &&
        this.$refs.swiper.stopTimer();
    }
  },
  destroyed() {
    // 停止轮播图(原生实现)
    this.stopTimer();
  }
};
</script>

<style lang="stylus" scoped>
.home
  position fixed
  width 100%
  top 0
  bottom 0
.slider-wrapper
  position relative
</style>

编译内容

在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值