编写可复用的组件在实际开发中是很是常见的需求,根据需求的不一样会有不少不一样的组件出现,有简单的好比Alert这样的弹窗组件,也有复杂些的日历组件等等css
今天咱们一块儿来写写组件(Vue组件),看看从中能不能掌握什么知识点吧!!!html
轮播图组件想必是使用最多的组件之一了,尤为是每一个首页当中一定会出现它的身影。vue
那么,咱们就闲言少叙了,实现一个适用于移动端上的轮播图组件吧node
先划个重点:要实现组件的开发,先简单看一下下面的3条逻辑webpack
核心逻辑ios
工欲善其事必先利其器,首先无论怎样,先把组件引入一番再说git
组件的使用向来是先引入再说,绝不例外的,看下面的组件引用github
// App.vue文件 -> js部分
<script>
// 引入Swiper和SwiperItem组件
import Swiper from './components/Swiper/Swiper';
import SwiperItem from './components/Swiper/SwiperItem';
export default {
// 组件内注册组件
components: {
Swiper,
SwiperItem
}
}
</script>
复制代码
js部分已经将引入的组件注册到了当前App组件中,下面就开始在模板中使用它们web
// App.vue文件 -> html部分
<template>
<div id="app">
<Swiper v-model="currentId">
<SwiperItem :id="currentId">
<div>第一张图</div>
</SwiperItem>
<SwiperItem :id="currentId">
<div>第二张图</div>
</SwiperItem>
<SwiperItem :id="currentId">
<div>第三张图</div>
</SwiperItem>
</Swiper>
</div>
</template>
复制代码
小朋友,你是否有不少问号?别着急,且听风吟vuex
Swiper组件里放了3个SwiperItem组件,并且SwiperItem组件里实现的内容是彻底相同的
因此,不必写3遍,直接用一个数组经过v-for
渲染出来就OK了
这里我简单用node写了个接口,返回轮播图的数据,若是不想写个接口的话,我直接把数据贴出来,让你们直观的看到
// 轮播图数据
const albums = [
{ "id": 1, "title": "叶惠美", "public": 2003, "song": '晴天', "img": "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=281827478,1045705647&fm=26&gp=0.jpg"},
{ "id": 2, "title": "七里香", "public": 2004, "song": '七里香', "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584873891083&di=7892d2142e6aba7e203d270e20599235&imgtype=0&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2Fc1505303db7c257f248adc87b6e22fd5.jpeg"},
{ "id": 3, "title": "十一月的萧邦", "public": 2005, "song": '夜曲', "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584873911041&di=51fc38d5805edc63fdd7301dbcef316f&imgtype=0&src=http%3A%2F%2Fzximg.ingping.com%2Fueditor%2Fjsp%2Fupload%2F201705%2F201705031153520358724.jpg"},
{ "id": 4, "title": "依然范特西", "public": 2006, "song": '听妈妈的话', "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1584873937432&di=ebf0092e78d5499f54728eeb43449414&imgtype=0&src=http%3A%2F%2Fimg1.dongqiudi.com%2Ffastdfs2%2FM00%2F66%2FBA%2FChOqM1rO_0mADp5rAACEhAjn7aY043.jpg"}
];
复制代码
我写的接口是/getalbums
,返回的是json格式,{code: 0, data: albumes}
下面我仍是按照正常的请求来操做,在以前的目录结构中,你们应该看到了api/shop.js
文件,这里是我封装好的用来请求轮播图数据的代码
// api/shop.js文件
import axios from 'axios';
// 拦截返回的响应数据
axios.interceptors.response.use(res => res.data);
export default {
async getAlbums() {
let { data } = await axios.get('/api/getalbums');
return data;
}
}
复制代码
如今让咱们回到App.vue这里,开始进行请求操做,而后v-for
遍历数组并渲染SwiperItem组件
// App.vue文件 -> js部分
<script>
import Swiper from './components/Swiper/Swiper';
import SwiperItem from './components/Swiper/SwiperItem';
// 引入封装好的请求方法
// @是webpack配的alias别名,指代src目录
import shop from '@/api/shop';
export default {
data() {
// 轮播图数据
sliders: [],
// 当前id,用来显示对应的图片
currentId: 1
},
async mounted() {
// 请求数据并更新slider数组
this.sliders = await shop.getAlbums();
},
components: {
Swiper,
SwiperItem
}
}
</script>
复制代码
渲染数据的部分要交给咱们的模板来处理了
// App.vue文件 -> html部分
<template>
<div id="app">
<Swiper v-model="currentId" v-if="sliders.length">
<template v-for="item in sliders">
<SwiperItem :key="item.id" :id="item.id">
<img :src="item.img" />
</SwiperItem>
</template>
</Swiper>
</div>
</template>
复制代码
上面模板为何写成这样?
v-model
也能够在组件上进行绑定
v-model
默认会利用名为value
的prop和名为input
的事件v-if
控制组件是在拿到数据后才进行渲染id
动态属性是用来区分当前应该展现哪张图片的重点好了,写到这里,基本的展现效果已经搞定了,img
元素的宽高,在App.vue的css部分能够定义一下如:#app img { width: 100%; height: 220px; }
,这样限制了宽高看起来就比较合适了
如今,开始尽请的开发吧
Swiper内部写了多个SwiperItem组件,因此须要一个slot
插槽来进行内容的分发,而且接收了v-model
绑定数据后传递过来的value
下面,来看一眼实现的逻辑吧
// Swiper.vue文件
<template>
<div class="swiper">
<div class="view">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
// v-model默认传递过来的value属性
value: {
type: Number,
default: 1
}
}
}
</script>
<style scoped>
.swiper {
position: relative;
width: 100%;
height: 220px;
overflow: hidden;
}
</style>
复制代码
先写了一个基本的逻辑,剩下的逻辑稍后再写,咱们再把SwiperItem组件实现一下
轮播图和选项卡很相似,都是当前只显示一张图,其他的都隐藏起来,SwiperItem组件就是用来作这些事情的
// SwiperItem.vue文件
<template>
<transition name="items">
<div class="swiper-item" v-if="isShow">
<slot></slot>
</div>
</transition>
</template>
<script>
export default {
props: {
id: {
type: Number,
// 必填属性
required: true
}
},
data() {
return {
selectedId: 1
}
},
computed: {
isShow() {
// 根据props传过来的值判断v-if显示
return this.id === this.selectedId;
}
}
}
</script>
<style scoped>
.items-enter-active,
.items-leave-active {
transition: .5s linear;
}
.items-leave-to {
transform: translateX(-100%);
}
.items-enter {
transform: translateX(100%);
}
.items-enter-active {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
</style>
复制代码
回到Swiper组件里,咱们须要写一个方法用来展现轮播图,那么就简单的起名为show方法吧,
它的做用就是:遍历$children
子组件实例,修改子组件里的selectedId的值,而后显示初始轮播图
// Swiper.vue文件
<script>
export default {
...省略,
data() {
return {
index: 1
}
},
methods: {
show() {
// 给index赋值为传递过来的value
// 若是没有传递value值,就默认取第一个子组件里的id值
this.index = this.value || this.$children[0].id;
// 遍历子组件,并修改实例上对应的selectedId值
this.$children.forEach(vm => {
vm.selectedId = this.index;
});
}
},
mounted() {
// 初始化
this.show();
}
}
</script>
复制代码
轮播图下方常见会有对应的小点,让用户直观的看到一共有几张轮播图片,其实实现起来很easy,由于只要咱们知道有几条数据就能够利用v-for
对应循环出来了
// Swiper.vue文件
<template>
<div class="swiper">
<div class="view">
<slot></slot>
</div>
<div class="dots">
<span class="dot"
v-for="dot in len"
:key="dot"
:class="{active: index === dot}">
</span>
</div>
</div>
</template>
<script>
export default {
...省略,
data() {
return {
index: 1,
// 记录轮播图的图片数量
// 定义在data是为了在模板使用
len: 0
}
},
methods: {
...省略
},
mounted() {
// 初始化
this.show();
// 更新len的长度,其实就是有几个子组件而已
this.len = this.$children.length;
}
}
</script>
<style scoped>
.swiper {
position: relative;
width: 100%;
height: 220px;
overflow: hidden;
}
/* 轮播图小点样式 */
.swiper .dots {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
.swiper .dots .dot {
width: 10px;
height: 4px;
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.7);
display: inline-block;
margin: 0 2px;
}
.swiper .dots .active {
width: 14px;
background-color: #fff;
}
</style>
复制代码
通过上面的一番折腾,总该有点该有的样子了吧,让咱们看看效果图
不少轮播图效果都是自带自动播放的,因此咱们当前也不会置之不理。咱们经过给Swiper组件传递autoplay
的prop来进行设置
// App.vue文件 -> html部分
<template>
<div id="app">
<Swiper v-model="currentId" v-if="sliders.length" :autoplay="true">
...省略
</Swiper>
</div>
</template>
复制代码
父组件App传递了autoplay
了,那么接下来就继续回到咱们的Swiper组件
咱们再实现一个play
方法,主要是写一个定时器,而后再经过change
方法来修改value的值,作到自动播放
// Swiper.vue文件 -> js部分
<script>
export default {
props: {
...省略,
autoplay: {
type: Boolean,
default: false
}
},
data() {
return {
index: 1,
len: 0
}
},
methods: {
show() {...省略},
// 定时器播放
play() {
if (this.autoplay) {
this.timer = setInterval(() => {
this.change(this.index + 1);
}, 3000);
}
},
// 切换轮播图
change(index) {
// 处理右边界若是超过了len
if (index === this.len + 1) {
// 将index改回到1
index = 1;
} else if (index === 0) {
index = this.len;
}
// 经过$emit,触发input事件,修改value为index值
this.$emit('input', index);
},
// 清除定时器
stop() {
clearInterval(this.timer);
this.timer = null;
}
},
watch: {
// 这里是关键
// 监听value值的变化,value赋给了index,随着每次的index变化
// 就从新调用一下show方法,刷新视图
value() {
this.show();
}
},
mounted() {
this.show();
this.len = this.$children.length;
// 调用自动播放
this.play();
}
}
</script>
复制代码
一块儿来看看效果吧
默认自动播放的时候,图片都是向左移动到-100%的位置。若是考虑到用户切换的话,须要先在mounted
时记录一下前一个索引值prevIndex
前一个索引值prevIndex
和当前的索引值index
去比较,若是大于了当前的index
,那么就说明是反向轮播,图片须要向右移动
让咱们再回到SwiperItem组件,给它添加一个动态class,用来显示正反方向的过渡效果
// SwiperItem.vue文件
<template>
<transition name="items">
<div class="swiper-item" v-if="isShow" :class="{direction}">
<slot></slot>
</div>
</transition>
</template>
<script>
export default {
...省略
data() {
return {
index: 1,
// 判断方向
direction: false
}
}
}
</script>
<style scoped>
...省略
/* 反向过渡 */
.items-leave-to.direction {
transform: translateX(100%);
}
.items-enter.direction {
transform: translateX(-100%);
}
</style>
复制代码
上面的代码为咱们修改了不一样方向的过渡效果,废话再也不多说,直接看代码吧
// Swiper.vue文件 -> js部分
<script>
export default {
...省略,
methods: {
show() {
this.index = this.value || this.$children[0].id;
this.$children.forEach(vm => {
this.$nextTick(() => {
vm.selectedId = this.index;
});
// 若是prevIndex大于了index就是反向轮播
vm.direction = this.prevIndex > this.index;
// 处理自动播放时的边界状况
if (this.timer) {
if (this.prevIndex === 1 && this.index === this.len) {
// 处理从第1张反向跳到最后一张的条件
vm.direction = true;
} else if (this.prevIndex === this.len && this.index === 1) {
// 处理最后一张正向跳到第一张的条件
vm.direction = false;
}
}
});
},
change(index) {
this.prevIndex = this.index;
...省略
}
},
mounted() {
this.show();
this.len = this.$children.length;
this.play();
// 先记录当前的值
this.prevIndex = this.index;
}
}
</script>
复制代码
简单梳理:
prevIndex
最开始是undefined,由于在mounted
按照顺序调用show
方法的时候,prevIndex
尚未赋值为index
prevIndex > index
第一次比较的时候,是undefined和1在比较,以后在每次切换图片,调用change
方法的时候又给prevIndex
赋了index
的值$nextTick
是为了在vm.direction
数据被修改了,等待DOM更新后再修改对应的selectedId
以对应展现对应图片this.prevIndex = this.index
就是在每次切换的时候把上一次的index
赋给了prevIndex
prevIndex
为undefined,index
为1;当prevIndex
为1的时候,index
为2,以此类推的赋值,这样你们就可以理解了吧至此,咱们把左右切换的过渡效果代码逻辑搞定了,还差最后一步了,让咱们给轮播图添加touch
事件,让用户能够左右切换图片
上面已经完成了判断方向的逻辑,用户左右切换简直是小菜一碟了,首先要给元素上添加touch事件
// Swiper.vue文件
<template>
<div class="swiper">
<div class="view" @touchstart="touchstart" @touchend="touchend">
<slot></slot>
</div>
...省略
</div>
</template>
<script>
export default {
...省略,
methods: {
touchstart(e) {
// 刚触摸屏幕时记录一个x坐标
this.startX = e.touches[0].pageX;
// 而且中止自动播放
this.stop();
},
touchend(e) {
// 计算开始点和手指抬起时离开点的坐标差值
let disX = e.changedTouches[0].pageX - this.startX;
// 若是小于0就表示是正向切换(向左滑动)
// 反之,就是反向切换(向右滑动)
if (disX < 0) {
this.change(this.index + 1);
} else {
this.change(this.index - 1);
}
// 切换完毕后,继续进行自动播放
this.play();
},
...省略
}
}
</script>
复制代码
写到这里就算完成了,让咱们再来一块儿看看效果吧
因为组件里用到了定时器,那么别忘记了在beforeDestroy
的时候,调用stop
方法清除一下定时器
原本还想写两个别的可复用组件,不过怕字数太多,你们看的疲劳了,就点到为止吧,我也会把相应的地址发出来,让你们方便参考
拾人牙慧,学无止境,感谢你们观看了