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>