效果图
布局
<div id="app">
<div class="c-box" :style="{width: itemWidth+'px'}">
<!-- 左切换按钮 -->
<div class="slide-left" @click="slideLeft"></div>
<!-- 显示内容窗口,overflow: hidden; -->
<div class="full-page win">
<!-- 盛放所有内容,左右滑动时设置 left 值显示不同页内容 -->
<div class="content-wrap" :style="{width: itemWidth*itemCount+'px', transition: wrapTransition}">
<div class="full-page item item_1" style="background: beige;">
<img src="./z1.jpg" alt="">
</div>
<div class="full-page item item_2" style="background: yellow;">默认显示第二页内容</div>
<div class="full-page item item_3" style="background: blue;">第三页内容</div>
<div class="full-page item item_4" style="background: green;">第四页内容</div>
</div>
</div>
<!-- 右切换按钮 -->
<div class="slide-right" @click="slideRight"></div>
</div>
</div>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.c-box {
height: 600px;
margin: 50px auto;
position: relative;
}
.slide-left, .slide-right {
width: 25px;
height: 80px;
border-radius: 2px;
background-color: black;
position: absolute;
top: 300px;
cursor: pointer;
z-index: 999;
}
.slide-left {
left: -30px;
}
.slide-right {
right: -40px;
}
.win {
border: 5px solid black;
overflow: hidden;
}
.content-wrap {
height: 100%;
position: relative;
display: flex;
transition: all .6s;
}
.full-page{
width: 100%;
height: 100%;
}
滑动原理解析
复制元素
- 当显示在第一页的时候如果继续点击“上一页”,应该显示的是最后一页,为了能平滑的到最后一页可以将最后一页的内容复制一份到第一页的前面
- 同理复制一份第一页的内容到最后一页的后面
cloneItem() {
const contentWrap = document.querySelector('.content-wrap')
const firstContent = document.querySelector('.item_1')
const lastContent = document.querySelector('.item_4')
const cloneFirst = firstContent.cloneNode(true)
const cloneLast = lastContent.cloneNode(true)
// 复制内容,将最后一页内容复制一份到第一页之前,将第一页内容复制一份到最后一页之后
contentWrap.insertBefore(cloneLast, firstContent)
contentWrap.appendChild(cloneFirst)
this.slideValue = -(this.itemWidth * 2)
contentWrap.style.left = -(this.itemWidth * 2) + 'px' // 默认显示第二页
this.itemCount += 2
},
slideValue
是当前偏移量,默认显示的是内容二页(没有偏移量显示的是复制的最后一页内容,-itemWidth
偏移量内容一页,-itemWidth*2
就是内容二页),后续需要这个值来判断当前偏移的操作itemCount
是此时的子元素的个数,默认是4个,加上此时已经复制的收尾2个子元素,后续需要通过这个值来计算偏移量
左右滑动
因为左右滑动原理相似,这里就只针对左滑动做个详细解析
slideLeft() {
const contentWrap = document.querySelector('.content-wrap')
this.slideValue += this.itemWidth
if(this.slideValue > 0) {
// 此时继续点击上一张应该是倒数第二张的内容,记住要取消滑动效果
this.wrapTransition = 'none'
contentWrap.style.left = -(this.itemWidth * (this.itemCount - 2)) + 'px'
this.slideValue = -(this.itemWidth * (this.itemCount - 3))
}
setTimeout(() => {
this.wrapTransition = 'left .6s'
contentWrap.style.left = this.slideValue + 'px'
}, 0)
},
- 当前默认显示内容二页,
slideValue
为-1800
- 点击“上一页”,显示内容一页,
slideValue
为-900
, - 再点击“上一页”,显示内容四页,
slideValue
为0
(因为内容四已经复制了一份在最前面) - 再点击“上一页”,进入
slideValue > 0
的判断,应该显示内容三页,此时要将其毫无渐变痕迹的转换到内容三页上去(这样才能实现循环)
毫无痕迹的转换的关键在于:当滑动到首位的页面四,此时继续点击“上一页”时,先取消渐变效果将偏移量设置到倒数第二页的页面四内容,然后再通过设置 setTimeout
添加渐变效果再使偏移量从页面四滑动到页面三。这样就神不知鬼不觉了。
注意:如果不设置setTimeout
,当你设置 this.wrapTransition = 'left .6s'
效果时会直接覆盖前面的this.wrapTransition = 'none'
操作(因为浏览器会将DOM操作批量集中起来一次动作,而不是每次赋值就动作一次,这也是它的提高效率优化的一种方法,所以这里需要加个事件循环,将这种累积操作阻断)
完整代码
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const {
createApp } = Vue
createApp({
data() {
return {
slideValue: 0, // 当前偏移量
itemWidth: 900, // 每个子元素的宽度
itemCount: 0, // 子元素个数
wrapTransition: 'left .6s'
}
},
mounted() {
this.$nextTick(() => {
this.itemCount = document.querySelectorAll('.item').length
this.cloneItem()
})
},
methods: {
cloneItem() {
const contentWrap = document.querySelector('.content-wrap')
const firstContent = document.querySelector('.item_1')
const lastContent = document.querySelector('.item_4')
const cloneFirst = firstContent.cloneNode(true)
const cloneLast = lastContent.cloneNode(true)
contentWrap.insertBefore(cloneLast, firstContent)
contentWrap.appendChild(cloneFirst)
this.slideValue = -(this.itemWidth * 2)
contentWrap.style.left = -(this.itemWidth * 2) + 'px' // 默认显示第二页
this.itemCount += 2
},
slideLeft() {
const contentWrap = document.querySelector('.content-wrap')
this.slideValue += this.itemWidth
if(this.slideValue > 0) {
this.wrapTransition = 'none'
contentWrap.style.left = -(this.itemWidth * (this.itemCount - 2)) + 'px'
this.slideValue = -(this.itemWidth * (this.itemCount - 3))
}
setTimeout(() => {
this.wrapTransition = 'left .6s'
contentWrap.style.left = this.slideValue + 'px'
}, 0)
},
slideRight() {
const contentWrap = document.querySelector('.content-wrap')
this.slideValue -= this.itemWidth
if(this.slideValue < -(this.itemWidth * (this.itemCount - 1))) {
this.wrapTransition = 'none'
contentWrap.style.left = -this.itemWidth + 'px'
this.slideValue = -this.itemWidth * 2
}
setTimeout(() => {
this.wrapTransition = 'left 0.6s'
contentWrap.style.left = this.slideValue + 'px'
}, 0)
}
}
}).mount('#app')
</script>