小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
TIP 👉 疾风知劲草,岁寒见后凋。范晔《后汉书》
前言
在我们日常项目开发中,我们经常会写一些图标,所以封装了这款图标组件。轮播图组件
属性
1. bannerList 数据数组
- 数组中存放轮播图片的信息
- 对象包含两个属性,src 和 link
- src 的值为图片地址
- link 的值为点击图片打开链接的地址
2. speed 动画时长
- 单位:毫秒
- 默认值:300
3. interval 轮播间隔时间
- 单位:毫秒
- 默认值:3000
4. defaultIndex 初始显示的轮播图的索引
默认值: 0
5. indexType 底部导航样式
- 底部导航样式值为dot 或 line
- dot 小圆点(默认值)
- line 短横线
示例
<template>
<div class="banners-demo">
<banners :bannerList="imgList" :speed="1000" :interval="5000" :defaultIndex="2" indexType="line"></banners>
</div>
</template>
<script>
import BaseBanners from '@/components/base/banners/index.vue'
export default {
name: 'bannersDemo',
data () {
return {
imgList: [
{
src: '/static/img/banner1.png',
link: 'http://www.baidu.com'
},
{
src: '/static/img/banner2.gif',
link: 'http://www.hao123.com'
},
{
src: '/static/img/banner3.jpg'
}
]
}
},
methods: {},
components: {
Banners
}
}
</script>
<style lang="scss" scoped>
.banners-demo{
width: 700px;
height: 400px;
margin: 30px auto 0;
}
</style>
实现banner.vue
<template>
<div class="banners-container">
<div class="banners-wrap" ref="bannersWrap">
<div class="banner-item" v-for="item in banners" :key="item.src"
:class="[item.animationClass]"
:style="item.style"
@click="linkHandle(item)">
</div>
</div>
<div class="nav-wrap">
<div class="nav-index-wrap" :class="[isStartNav && index === curIndex ? 'cur' : '']" v-for="(item, index) in bannerList" :key="item.src" @click="onChooseIndex(index)">
<b class="nav-index" :class="indexClass">
<span class="line-process" v-if="indexType === 'line'" :style="{transitionDuration: interval+'ms'}"></span>
</b>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'banners',
props: {
bannerList: {
type: Array,
default: () => []
},
// 动画时长(毫秒)
speed: {
type: Number,
default: 300
},
// 轮播间隔时间 (毫秒)
interval: {
type: Number,
default: 3000
},
// 初始显示的轮播图的索引
defaultIndex: {
type: Number,
default: 0
},
// 底部导航样式:dot(圆点)、line(横线),默认:dot
indexType: {
type: String,
default: 'dot'
}
},
data () {
const curIndex = (this.defaultIndex < 0 || this.defaultIndex >= this.bannerList.length) ? 0 : this.defaultIndex
return {
curIndex, // 当前索引
lastIndex: null, // 上一个banner的索引
intervalId: null, // 定时器ID
isStart: false, // 是否开始banner切换动画
isStartNav: false, // 是否开始导航动画
isReverse: false // 动画是否反向
}
},
computed: {
indexClass () {
return 'nav-' + this.indexType
},
banners () {
let list = this.bannerList
let length = this.bannerList.length
for (let i = 0; i < length; i++) {
let animationClass = ''
let style = {
animationDuration: this.speed + 'ms',
backgroundImage: `url('${list[i].src}')`
}
if (this.isStart) {
if (this.isReverse) {
if (i === this.curIndex) {
animationClass = 'show-reverse'
} else if (i === this.lastIndex) { // i === (this.curIndex + 1) % length)
animationClass = 'hide-reverse'
}
} else {
if (i === this.curIndex) {
animationClass = 'show'
} else if (i === this.lastIndex) { // (i === (this.curIndex === 0 ? length - 1 : this.curIndex - 1))
animationClass = 'hide'
}
}
} else {
if (i === this.curIndex) {
animationClass = 'cur'
}
}
list[i].animationClass = animationClass
list[i].style = style
}
return list
}
},
mounted () {
setTimeout(() => { this.isStartNav = true }, 10)
this.intervalId = setInterval(this.start, this.interval)
this.addListener()
},
methods: {
linkHandle (item) {
if (item.link) {
window.open(item.link)
}
},
start () {
this.isStart = true
this.isReverse = false
this.lastIndex = this.curIndex
let maxIndex = this.bannerList.length - 1
if (this.curIndex >= maxIndex) {
this.curIndex = 0
} else {
this.curIndex++
}
},
onSwipe (direction) {
if (direction) {
clearInterval(this.intervalId)
this.lastIndex = this.curIndex
this.isReverse = direction < 0
this.curIndex += direction
let listLength = this.bannerList.length
if (this.curIndex >= listLength) {
this.curIndex = 0
} if (this.curIndex < 0) {
this.curIndex = listLength - 1
}
this.isStart = true
this.intervalId = setInterval(this.start, this.interval)
}
},
onChooseIndex (index) {
if (this.curIndex !== index) {
clearInterval(this.intervalId)
this.lastIndex = this.curIndex
this.curIndex = index
this.isStart = true
this.intervalId = setInterval(this.start, this.interval)
}
},
addListener () {
const bannersWrap = this.$refs.bannersWrap
bannersWrap.addEventListener('touchstart', this.touchStartHandler)
bannersWrap.addEventListener('touchmove', this.touchMoveHandler)
bannersWrap.addEventListener('touchend', this.touchEndHandler)
},
touchStartHandler (e) {
this.startX = e.touches[0].pageX
this.moveEndX = this.startX
},
touchMoveHandler (e) {
this.moveEndX = e.changedTouches[0].pageX
},
touchEndHandler (e) {
if (this.moveEndX - this.startX > 50) {
this.onSwipe(-1)
e.stopPropagation()
} else if (this.moveEndX - this.startX < -50) {
this.onSwipe(1)
e.stopPropagation()
}
}
},
destroyed () {
clearInterval(this.intervalId)
}
}
</script>
<style lang="scss" scoped px2rem="false">
$dotSize: 6px;
.banners-container{
position: relative;
height: 100%;
}
.banners-wrap{
position: relative;
overflow: hidden;
height: 100%;
white-space: nowrap;
background-color: #cdcdcd;
.banner-item{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #000 no-repeat center;
background-size: cover;
animation-timing-function: ease-in-out;
z-index: 0;
}
.cur,.show,.show-reverse{
z-index: 2;
}
.hide, .hide-reverse{
z-index: 1;
}
.show{
animation-name: show-in;
}
.hide{
animation-name: show-out;
transform: translate3d(-100%, 0, 0);
}
.show-reverse{
animation-name: show-in-reverse;
}
.hide-reverse{
animation-name: show-out-reverse;
transform: translate3d(100%, 0, 0);
}
}
.nav-wrap{
position: absolute;
bottom: 14px;
width: 100%;
text-align: center;
white-space: nowrap;
line-height: $dotSize * 2;
z-index: 10;
.nav-index-wrap {
display: inline-block;
.nav-index {
display: inline-block;
vertical-align: middle;
}
.nav-dot{
margin: 5px 10px;
width: $dotSize * 2;
height: $dotSize * 2;
border-radius: 50%;
background-color: rgba(0,0,0,.5);
}
.nav-line{
margin: 5px;
width: 40px;
height: 2px;
background-color: rgba(255, 255, 255, .5);
.line-process {
display: block;
height: 100%;
width: 0;
background-color: #fff;
transition: none;
}
}
}
.cur{
.nav-dot {
background-color: rgba(255, 255, 255, .5);
}
.nav-line{
background-color: rgba(255, 255, 255, .5);
.line-process {
width: 100%;
transition-property: width;
transition-timing-function: ease;
}
}
}
}
@keyframes show-in{
0% {
transform: translate3d(100%, 0, 0);
}
100% {
transform: translate3d(1px, 0, 0);
}
}
@keyframes show-out{
0% {
transform: translate3d(1px, 0, 0);
}
100% {
transform: translate3d(-100%, 0, 0);
}
}
@keyframes show-in-reverse{
0% {
transform: translate3d(-100%, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
@keyframes show-out-reverse{
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(100%, 0, 0);
}
}
</style>
// 下面注释勿删,用于根据配置文件 isMobile 配置项判断是否支持移动端,如果不支持,注释掉该样式
/* [auto html command START {isMobile=false}] */
<style lang="scss" scoped>
$dotSize: 8px;
@media (max-width: $max-mobile-width) {
.nav-wrap{
bottom: 10px;
line-height: $dotSize * 2;/*no*/
.nav-index-wrap {
.nav-dot {
width: $dotSize * 2; /*no*/
height: $dotSize * 2; /*no*/
border-radius: 50%; /*no*/
}
.nav-line {
margin: 10px;
width: 40px;
height: 4px;
}
}
}
[data-dpr='1'] .nav-wrap{
line-height: $dotSize;/*no*/
.nav-dot{
width: $dotSize;/*no*/
height: $dotSize;/*no*/
}
}
[data-dpr='3'] .nav-wrap{
line-height: $dotSize * 3;/*no*/
.nav-dot{
width: $dotSize * 3;/*no*/
height: $dotSize * 3;/*no*/
}
}
}
</style>
/* [auto html command END {isMobile=false}] */
感谢评论区大佬的点拨。