轮播图组件是大多数项目都有的需求,当然相关的库、插件多的很,但是呢我还是喜欢自己折腾一下。
需求 在做公司项目时其实需求很简单就是能滑动轮播,自动轮播,循环轮播完事了。
展示地址: http://vuedemo.fgdemoshow.cn/swiper.
这里的是手机项目用pc的朋友切换手机模式偶
码云: https://gitee.com/lifei5859/vue-plugin/tree/master/src/components/slider.
github老上不去切码云了
代码 首先html部分
class="swiper-sliber" />
class="paging">{{pagingText !== 'none'? pagingText : ''}}{{loop ? length-2 + '/' + index : length + '/' + (index + 1)}}
-
我自己写了切换按钮,然而在手机项目的情况下。。。
然后js部分:
>
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居中 可选leftright
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)
}
}
这个要和mySwiper配合使用
mySwiperItem.vue
:ref="'item' + name">
>
export default {
name: "my-swiper-item",
props: {
width: {
type: Number,
required: true
},
height: {
type: String,
default: '4'
}
}
}
这个呢,缩进上有点不是很满意,但是暂时没有想到更好的办法。。。。
mySwipr配置项
配置项 | type | default | 作用 | 可选项 |
---|---|---|---|---|
oHeight | String | ‘auto’ | 设置组件高度 | ‘xxxrem’: xxx为你要设置的值 |
nextBtn | Boolean | false | 是否显示切换按钮 | true显示/false不显示 |
loop | Boolean | false | 是否开启循环轮播 | true开启/false不开启 |
paging | String | ‘center’ | 是否显示分页器 | 'left’左边显示/'right’右边显示/'center’居中/'none’隐藏 |
pagingText | String | ‘none’ | 是否显示分页文字 | 'none’不显示 其他字符串为分页文字前缀 |
pagingPosition | String | ‘center’ | 分页文字位置 | 'center’居中/'left’左边/'right’右边 |
defaultIndex | Number | 0 | 组件加载完成时轮播索引 | 数字为组件从哪个索引开始轮播不要大于轮播页总数 |
automation | Number | 0 | 自动轮播间隔时长 | 0不自动轮播,传入数字为轮播时长,单位ms |
zoom | Boolean | false | 是否开启zoom模式 | 要配合子组件宽度一起使用 必须在loop模式下开启 |
retract | Boolean | false | 是否开启zoom模式 | 要配合子组件宽度一起使用 必须在loop模式下开启 |
mySwiprItem配置项
配置项 | type | default | 作用 | 可选项 |
---|---|---|---|---|
width | Number | 无 | 组件宽度 | 必选项一般就是父级组件的宽度即可,这里可以和mySwiper的缩进模式配合使用 |
oHeight | String | ‘4’ | 组件高度 | ‘xxxrem’: xxx为你要设置的值 |
这里的是手机项目用pc的朋友切换手机模式偶
码云: https://gitee.com/lifei5859/vue-plugin/tree/master/src/components/slider.
github老上不去切码云了
附上一个手写confirm弹窗 展示地址:http://vuedemo.fgdemoshow.cn/confirm
码云: https://gitee.com/lifei5859/vue-plugin/tree/master/src
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- vue.js|vue中使用axios封装成request使用
- JavaScript|JavaScript: BOM对象 和 DOM 对象的增删改查