vue轮播图演示
@vue/cli 5.0.6 vue@2.7.0
1:css样式
.rotation-wrapper {
margin-left: -100%;
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: flex; //父级盒子设置flex布局
transition-property: transform;
box-sizing: content-box;
}
.rotation-slide {
flex-shrink: 0; //子元素flex-shrink:0表示子元素有自己的宽度,不需要flex自动计算子元素的宽度
width: 100%;
height: 100%;
position: relative;
transition-property: transform;
}
/* flex-shrink是样式中的核心属性 */
-
父级盒子使用flex布局
-
子盒子需要设置flex-shrink为0,不需要flex布局为图片计算宽度
2:需要用到的状态
data() {
return {
imagesIsOk: false, // 控制轮播图组件是否显示
visibilityFlag: true, // 只有在当前路由组件离开页面后再进入页面才添加定时器,在其它路由组件离开页面后进入页面不添加定时器
num: 1, // 控制组件激活时添加定时器(第二次或以上进入该组件时添加定时器,包括第二次)
current: 0, // 轮播图计数器
rotationWidth: 0, // 轮播图的宽度
movingDistance: 0, // 经过计算后轮播图在x轴上移动的距离
rotationWrapperStyle: {}, // 轮播图移动距离样式和动画过渡样式
timer: null, // 轮播图定时器(timer数据来源:watch中的监听banners、组件第二次激活、进入页面、触摸结束)
startX: 0, // 记录触摸开始的坐标(相对于轮播图盒子的位置)(可能是正数,也可能是负数)(向右滑动是正数,向左滑动是负数)
moveX: 0, // 计算滑动的距离
moveFlag: false // 触摸滑动轮播图开关,产生滑动时为true,方便触摸结束后计算是否移动到下一张还是维持当前图片
};
},
-
imagesIsOk (某些情况下轮播图的图片或者图片链接地址是从服务器请求下来的,这时就是异步任务,为了保证后续其它操作能正 常,我们需要给轮播图组件一个开关,确保异步任务完成后再显示轮播图)
-
visibilityFlag (当页面处于不可见时,计时器还在运行,但是DOM元素却不再刷新,这样就会对轮播图的效果产生影响,我们使用此状态是为了让页面处于不可见时清除定时器,处于可见时启动定时器)
-
num (首次打开页面时,组件会被激活一次(组件激活时也会添加定时器),但是我们在watch里对imagesIsOk进行了监控,为true时启动定时器(为true时表示异步任务完成),所以我们不希望首次进入页面组件激活时就启动定时器,因为这可能导致DOM元素操作上有问题,而当组件处于失活状态是我们改变num的值为2,用来表示不是第一次进入该组件,此时在组件激活时判断num是否等于2就可以启动定时器了)
-
current (这是一个非常关键的计数器,它用来控制轮播图走到第几张,小圆点第几个改变背景颜色)
-
rotationWidth (用来获取轮播图元素的宽度,我们还在组件挂载时添加了一个窗口变化事件window.onresize,当窗口大小发生变化时,重新获取元素的宽度(元素宽度是百分比))
-
movingDistance (用来存放计算后轮播图移动的距离了,它的数值是通过current和rotationWidth计算而来)
-
rotationWrapperStyle (这是一个对象数据,里面存放的是轮播图切换需要移动距离的样式,移动距离从movingDistance里来,)
-
timer (里面存放的一个连续的定时器,用来播放图片,每隔多长时间切换一张图片,起始值为空是为了防止异步任务没有完成就启动定时器到时操作不准确,timer的数据来源有很多个地方:watch监听中异步任务完成时添加定时器、组件激活时添加定时器、页面可见时添加定时器、触摸结束时添加定时器(相应的在组件失活时清除定时器、组件不可见时清除定时器、触摸发生时清除定时器))
-
startX (记录了触摸开始时的坐标,这个数据不会改变,除非再次触摸)
-
moveX (记录里触摸滑动的距离,结果是用滑动的坐标减去startX的坐标得到,只要一直发生就会不断刷新滑动坐标)
-
moveFlag (默认为false,当发生触摸滑动时修改为true,为true后便于触摸结束的语句判断)
3:拆分各大功能
1:监听异步任务完成(写在watch里)
watch: {
'banners': {
handler() {
this.$nextTick(() => {
if (this.banners.length > 0) {
/*这里为什么用this.$refs.rotation.getBoundingClientRect().width获取元素宽度?????*/
/*因为:浏览器渲染元素时可能出现小数位,通过此方法获取最真实的元素宽度(带小数位)。普通的offsetWidth获取到的宽度是四舍五入后的值,
这样会导致轮播图右侧出现多移动或者左侧少移动的问题,轮播图图片越多此问题越明显*/
this.rotationWidth = this.$refs.rotation.getBoundingClientRect().width
this.num = 2
this.imagesIsOk = true
this.timer = setInterval(() => {
this.current++
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'all 0.3s'
}
}, this.timeInterval)
}
})
},
immediate: true
}
}
//为什么要用immediate:确保轮播图传入静态数据也可以正确监听,并配合this.$nextTick实现静态资源的轮播
-
监听props里的banners数据的变化,确保异步任务完成后才启动定时器
-
当banners里的数组发生变化后,并且数组长度大于0进入if的语句里
-
获取轮播图DOM元素的宽度数据并保存在rotationWidth里
-
让num等于2,便于组件激活判断是否需要添加定时器(第一次进入路由组件时需要判断异步任务是否完成,所以首次进入页面不需要在activated生命周期函数中添加定时器,而第二次激活路由组件时num等于2,则需要添加定时器)
-
将imagesIsOk修改为true,为true时轮播图组件显示
-
添加轮播图定时器
2:自动轮播功能(写在methods里)
this.timer = setInterval(() => {
this.current++
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'all 0.3s'
}
}, this.timeInterval)
-
向状态timer里添加一个循环定时器,先对current加1,然后调用移动距离计算方法,然后改变rotationWraperStyle里的数据,一个为transform一个为transition(transition属性必须添加,因为在动画结束后一个无缝判断事件需要依赖此属性)
-
这个轮播定时器在watch里监控异步任务完成时会添加、组件激活时会添加、页面可见时会添加、触摸结束时会添加(除了watch里不移除定时器,其它三项都有对应的移除定时器)
-
间隔时间this.timeInterval是从组件调用时传入到props里的数据,如果没有传入,默认是3000毫秒
3:计算移动距离(写在methods里)
rotationWrapper() { //计算移动距离
this.movingDistance = -this.current * this.rotationWidth
},
4:修改小圆点背景颜色(写在methods里)
changeDotColor() { //修改小圆点背景颜色
if (this.dotFlag) {
for (let i of this.$refs.rotationDot) {
i.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'
}
this.$refs.rotationDot[this.current].style.backgroundColor = '#97529c'
}
},
-
dotFlag如果为true则表示底部小圆点显示,这个变量是从组件调用时传入的,如果不传入默认是true,如果为false小圆点不显示,那也就没有必要去修改它的背景颜色了
-
通过排他思想将所有的小圆点的背景颜色修改为不激活的颜色
-
通过$refs获取小圆点元素,数据是一个数组,我们通过把current当做数组的索引值刚好可以对应数组的索引值,这样第二张图片显示时,第二个小圆点也就有了激活后的背景颜色(current的初始值是0)
-
我们在css样式中需要用nth-child将第一个小圆点的背景颜色直接修改为激活后的颜色(默认显示的就是第一张图片,那么第一个小圆点就是处于激活状态)
5:无缝过渡判断(写在methods里)(重要逻辑)
seamlessTransition() {
if (this.current === this.banners.length) {
this.current = 0
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'none'
}
} else if (this.current < 0) {
this.current = this.banners.length - 1
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'none'
}
}
this.changeDotColor()
}
-
这是轮播图组件中核心的判断逻辑
-
无缝过渡的前提:将轮播图的第一张图片复制到最后,将最后一张轮播图复制到最前
<div class="swiper">
<div><img src="复制数组里最后一张图片"></div>
<div v-for="item in banners" v-bind:key="item.id"><img src="item.link"></div>
<div><img src="复制数组里第一张图片"></div>
</div>
-
如果轮播图计数器current等于了banners数组的长度
-
current赋值为0
-
调用一次计算移动距离方法 rotationWrapper() 计算重新赋值后的current计算后的移动距离
-
将rotationWrapperStyle里的内容重新赋值(因为是使用v-bind绑定的,当数据放生变化页面也发生变化)
-
为什么transition:'none'?
-
因为无缝过渡的方法是在动画走完后触发事件"transitionend",当current等于了banners的数组长度时,说明已经走到了最后一张(也就是复制第一张图片放在最后面),而当动画结束进入判断时,刚好current等于了banners的数组长度,我们又通过重新赋值后的current计算了移动距离,而这个距离也就是第一张图片(通过遍历的第一张图片),又因为此时过渡动画刚刚走完,我们就可以直接将移动距离赋值,并且将transition赋值为空,就可以瞬间跳转到第一张图片,这样就可以实现无缝过渡,让用户感受不到图片的生硬切换,而是平滑的过渡到第一张
-
-
-
当轮播图计数器current小于了0
-
当轮播图计数小于0,也就是负数时,就是发生了触摸滑动操作,是用户想看上一张照片(当滑动到数组里的第一张图片并且继续滑动时,就会看见复制最后一张图片放在最前面的那张)
-
我们让current重新赋值,让banners数组长度减1,也就是刚好得到数组最后一位索引值
-
用重新赋值的current进行移动距离计算
-
将计算后的移动距离重新添加到样式中,同样transition:'none',为什么这么做和上面current等于数组长度是一样的,只不过这里是为了无缝过渡到最后一张图片
-
-
通过重新赋值的current来给指定的小圆点改变背景颜色(因为在上面的if和else if中已经判断了current究竟是等于0还是等于banners数组长度减1)
6:触摸开始
contentTouchStart(e) { //触摸开始 记录触摸开始的位置 并清除定时器
e.preventDefault()
clearTimeout(this.touchTimer)
this.touchTimer = null
clearInterval(this.timer)
this.timer = null
this.startX = e.targetTouches[0].pageX
},
-
当触摸发生时,用e.preventDefault()阻止默认事件,防止因为触摸导致点击事件的触发
-
当触摸发生时,清除触摸结束时添加的定时器,并将定时器变量赋值为空
-
当触发发生时,清除原有的定时器,也就是没法发生触摸前添加的定时器,并且将定时器变量赋值为空
-
用startX把触摸开始的坐标记录下来,用于计算触摸滑动距离
7:触摸滑动
contentTouchMove(e) { //触摸滑动
e.preventDefault()
this.moveX = e.targetTouches[0].pageX - this.startX
this.rotationWrapper()
this.movingDistance = this.movingDistance + this.moveX
this.rotationWrapperStyle = {
transition: 'none',
transform: 'translate3d(' + this.movingDistance + 'px,0,0)'
}
this.moveFlag = true
},
-
当触摸滑动时,用e.preventDefault()阻止默认事件,防止因为触摸导致点击事件的触发
-
用滑动的坐标减去触摸开始时记录的startX,得到moveX,也就是触摸滑动了多远的距离(只要滑动还在继续发生,滑动的坐标就会一直发生变化)
-
调用移动距离计算方法,获得当前的移动距离,并用当前移动距离(其实就是当前轮播图的X轴坐标)加上moveX(滑动移动距离),这样就可以让当前图片移动距离和用户滑动距离相等
-
此时移动的样式中只需要距离,不需要过渡动画,所以transition:'none'
-
moveFlag修改为true,便于触摸结束时进行判断
8:触摸结束
contentTouchEnd() { //触摸结束
if (this.moveFlag) {
if (Math.abs(this.moveX) > 50)
if (this.moveX > 0) {
this.current--
} else {
this.current++
}
this.rotationWrapper()
this.rotationWrapperStyle = {
transition: 'all 0.3s',
transform: 'translate3d(' + this.movingDistance + 'px,0,0)'
}
} else {
this.rotationWrapper()
this.rotationWrapperStyle = {
transition: 'all 0.3s',
transform: 'translate3d(' + this.movingDistance + 'px,0,0)'
}
}
this.touchTimer = setTimeout(() => {
this.timer = setInterval(() => {
this.current++
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'all 0.3s'
}
}, this.timeInterval)
}, 1000)
}
},
-
触摸结束时首先判断moveFlag是否为true,是true才进入语句
-
对moveX去绝对值,因为用户滑动可以是左可以是右,moveX的值也可能是正数也可能是负数,如果moveX大于50,也就是表示用户滑动距离大于了50像素,进入判断(让触摸滑动轮播图更加具有逻辑性,大于50像素时才跳动到上一张或下一张)
-
如果moveX的值大于0,也就是moveX是正数,用户是向右滑动的,想看上一张图片,current减1
-
如果moveX的值小于0,也就是moveX是负数,用户是向左滑动的,想看下一张图片,current加1
-
-
判断完成移动到上一张图片后者下一张图片
-
如果moveX取绝对值小于了50说明用户移动的距离不够,返回到当前的图片
-
添加一个1秒后启动的定时器,启动的就是轮播图定时器
-
9:组件激活时逻辑(activated()生命周期函数)
activated() {
this.visibilityFlag = true
if (this.num === 2) {
this.timer = setInterval(() => {
this.current++
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'all 0.3s'
}
}, 3000)
}
},
-
在组件激活时,visibilityFlag修改为true,表示当前路由组件处于显示状态
-
这个状态主要作用:确保在当前路由组件激活是页面不可见清除定时器,可见添加定时器。
-
如果此路由组件处于失活状态,页面不可见时不清楚定时器,页面不可见时不添加定时器
-
-
判断num是否等于2
-
原因是当首次打开页面时,路由激活声明周期函数是会被调用一次的,而我们不希望定时器在此时添加,因为有异步任务需要watch去监控它
-
首次开开页面时num是1,所以不添加定时器,当watch监控到banners里数据变化后添加定时器并将num修改为2,所以此时才需要添加定时器(代表第二次激活次路由组件)
-
-
添加轮播图定时器
10:组件失活时逻辑
deactivated() {
this.visibilityFlag = false
clearInterval(this.timer)
this.timer = null
}
-
组件失活时修改visibilityFlag为false,主要是为组件第二次激活时添加定时器
-
清除定时器
11:组件挂载时逻辑
mounted() {
document.addEventListener('visibilitychange', () => {
if (this.visibilityFlag) {
if (document.hidden) {
clearInterval(this.timer)
this.timer = null
} else {
this.timer = setInterval(() => {
this.current++
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'all 0.3s'
}
}, this.timeInterval)
}
}
})
window.onresize = () => {
this.rotationWidth = this.$refs.rotation.getBoundingClientRect().width
}
},
-
向document绑定事件visivilitychange事件(页面可见性事件)
-
如果visibilityFlag为true进入语句(也就是当前路由组件活跃时才在页面可见时添加定时器)
-
继续判断 如果document.hidden为true(document.hidden是页面可见还是不可见,不可见为true,可见为false)时,清除定时器,并且将定时器赋值为null
-
如果document.hidden为false添加定时器(this.timeInterval是组件调用时传入props里的,默认值3000)
-
添加window.onresize事件,监控浏览器窗口变换,窗口变化时,重新获取轮播图的宽度(重新获取宽度后轮播图移动位置会变准确。可在浏览器的收集模式下演示此功能,随意切换手机型号后,轮播图的移动距离也是准确的)
4:注意点
1:移动端触摸事件可能导致a标签无法点击
-
解决方法:不要在触摸开始时阻止默认行为,值在触摸滑动时阻止默认行为
2:定时器在不要时要清除,不然容易引起DOM操作不正确的问题
-
在页面不可见,路由组件失活或路由组件销毁,触摸开始时都要清除定时器,并且保证同时只能有一个定时器存在
3:触摸事件要写在有overflow的元素上,不然ios移动端浏览器不能触发事件
.rotation {
margin: 10px auto;
border-radius: 10px;
position: relative;
overflow: hidden;
width: 100%;
z-index: 1;
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.3);
<div class="rotation"
v-on:touchstart="contentTouchStart($event)"
v-on:touchmove="contentTouchMove($event)"
v-on:touchend="contentTouchEnd($event)"
ref="rotation">
</div>
4:定时器的添加和清除一定要梳理清楚
-
定时器同时只能存在一个
-
路由组件失活或者组件销毁都要清除定时器
-
整个页面不可见时需要清除定时器(并且确保是在当前组件页面不可见)
5:组件首次激活时不能通过激活时的生命周期函数添加定时器
-
组件首次激活时需要等待异步任务完成后添加定时器
5:源代码
<template>
<div class="rotation"
ref="rotation"
v-on:touchstart="contentTouchStart($event)"
v-on:touchmove="contentTouchMove($event)"
v-on:touchend="contentTouchEnd($event)">
<div class="rotation-wrapper"
ref="rotation-wrapper"
v-if="imagesIsOk"
v-on:transitionend="seamlessTransition"
v-bind:style="rotationWrapperStyle"> <!--动态绑定style,使用imagesIsOk控制是否显示,绑定事件:过渡结束、触摸开始、触摸移动、触摸结束-->
<div class="rotation-slide"> <!--复制最后一张图片到第一张,做无缝过渡-->
<a>
<img v-bind:src="banners[banners.length - 1]" alt=""/>
</a>
</div>
<div class="rotation-slide" v-for="item in banners"> <!--复制最后一张图片到第一张,做无缝过渡-->
<a>
<img v-bind:src="item" alt=""/>
</a>
</div>
<div class="rotation-slide"> <!--复制第一张图片到最后一张,做无缝过渡-->
<a>
<img v-bind:src="banners[0]" alt=""/>
</a>
</div>
</div>
<div class="defaultPicture" v-if="banners.length===0">
<img src="" alt=""><!--如果轮播图数据还没就位,就显示默认图片-->
</div>
<div class="rotation-pagination" v-if="imagesIsOk && dotFlag"><!--动态生成小圆点-->
<span class="rotation-dot" v-for="item in banners" ref="rotationDot"></span>
</div>
</div>
</template>
<script>
export default {
name: "rotation",
data() {
return {
imagesIsOk: false, // 控制轮播图组件是否显示(确保banners里有数据再显示)
visibilityFlag: true, // 只有在当前路由组件离开页面后再进入页面才添加定时器,在其它路由组件离开页面后进入页面不添加定时器
activeNum: 1, // 控制组件激活时添加定时器(第2次或以上进入该组件时添加定时器,包括第2次)
current: 0, // 轮播图计数器
rotationWidth: 0, // 轮播图的宽度
movingDistance: 0, // 经过计算后轮播图在x轴上移动的距离
rotationWrapperStyle: {}, // 轮播图移动距离样式和动画过渡样式
timer: null, // 轮播图定时器(timer数据来源:watch中的监听banners、组件第二次激活、进入页面、触摸结束)
startX: 0, // 记录触摸开始的坐标(相对于轮播图盒子的位置)(可能是正数,也可能是负数)(向右滑动是正数,向左滑动是负数)
moveX: 0, // 计算滑动的距离
moveFlag: false // 触摸滑动轮播图开关,产生滑动时为true,方便触摸结束后计算是否移动到下一张还是维持当前图片
};
},
props: {
banners: { /*传入的轮播图组件图片数据,必须是数组格式*/
type: Array,
required: true /*必传参数*/
},
timeInterval: { /*传入的轮播图时间间隔,不传则默认4000毫秒*/
type: Number,
default: 4000
},
dotFlag: { /*是否显示小圆点 默认显示*/
type: Boolean,
default: true
}
},
watch: {
'banners': { /*监控props里的banners数据变化,一旦发生变化则获取元素宽度,计算移动距离,并添加轮播定时器*/
handler() {
this.$nextTick(() => {
if (this.banners.length > 0) {
/*这里为什么用this.$refs.rotation.getBoundingClientRect().width获取元素宽度?????*/
/*因为:浏览器渲染元素时可能出现小数位,通过此方法获取最真实的元素宽度(带小数位)。普通的offsetWidth获取到的宽度是四舍五入后的值,
这样会导致轮播图右侧出现多移动或者左侧少移动的问题,轮播图图片越多此问题越明显*/
this.rotationWidth = this.$refs.rotation.getBoundingClientRect().width
this.num = 2
this.imagesIsOk = true
this.addTimer()
}
})
},
immediate: true /*确保能监听到静态资源传入props内(如果是异步任务这里可以删除)*/
}
},
methods: {
addTimer() { /*添加轮播图定时器*/
this.timer = setInterval(() => {
this.current++
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'all 0.3s'
}
}, this.timeInterval)
},
clearTimer() { /*清除轮播图定时器*/
clearInterval(this.timer)
this.timer = null
},
rotationWrapper() { /*计算图片移动距离*/
this.movingDistance = -this.current * this.rotationWidth
},
contentTouchStart(e) { /*触摸开始时*/
this.clearTimer()
this.startX = e.targetTouches[0].pageX
},
contentTouchMove(e) { /*触摸滑动时*/
e.preventDefault()
this.moveX = e.targetTouches[0].pageX - this.startX
this.rotationWrapper()
this.movingDistance = this.movingDistance + this.moveX
this.rotationWrapperStyle = {
transition: 'none',
transform: 'translate3d(' + this.movingDistance + 'px,0,0)'
}
this.moveFlag = true
},
contentTouchEnd() { /*触摸结束时*/
if (this.moveFlag) {
if (Math.abs(this.moveX) > 50) {
if (this.moveX > 0) {
this.current--
} else {
this.current++
}
this.rotationWrapper()
this.rotationWrapperStyle = {
transition: 'all 0.3s',
transform: 'translate3d(' + this.movingDistance + 'px,0,0)'
}
} else {
this.rotationWrapper()
this.rotationWrapperStyle = {
transition: 'all 0.3s',
transform: 'translate3d(' + this.movingDistance + 'px,0,0)'
}
}
this.addTimer()
}
},
changeDotColor() { /*轮播图变化后修改小圆点背景颜色*/
if (this.dotFlag) {
for (let i of this.$refs.rotationDot) {
i.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'
}
this.$refs.rotationDot[this.current].style.backgroundColor = '#97529c'
}
},
seamlessTransition() { /*无缝过渡*/
if (this.current === this.banners.length) {
this.current = 0
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'none'
}
} else if (this.current < 0) {
this.current = this.banners.length - 1
this.rotationWrapper()
this.rotationWrapperStyle = {
transform: 'translate3d(' + this.movingDistance + 'px,0,0)',
transition: 'none'
}
}
this.changeDotColor()
}
},
mounted() { /*挂载时*/
window.onresize = () => { //窗口发生变化时重新获取rotationWidth
this.rotationWidth = this.$refs.rotation.getBoundingClientRect().width
}
document.addEventListener('visibilitychange', () => {//添加页面可见性事件
if (this.visibilityFlag) {
if (document.hidden) {
this.clearTimer()
} else {
this.addTimer()
}
}
})
},
activated() { /*组件激活时*/
this.visibilityFlag = true
if (this.activeNum === 2) {
this.addTimer()
}
},
deactivated() { /*组件失活时*/
this.clearTimer()
this.visibilityFlag = false
this.homeActive = false
window.onresize = null
},
beforeDestroy() { /*组件销毁时*/
this.clearTimer()
this.visibilityFlag = false
this.homeActive = false
window.onresize = null
}
}
</script>
<style scoped lang="less">
.rotation {
margin: 10px auto;
border-radius: 10px;
position: relative;
overflow: hidden;
-webkit-backface-visibility: hidden; //解决IOS端overflow失效的问题
transform: translateZ(0); //解决ios端overflow失效的问题
width: 100%;
box-shadow: var(--element-shadow);
height: 95vw;
.defaultPicture {
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.rotation-wrapper {
margin-left: -100%; //因为第一张图片是从最后一张复制的,所以需要将盒子移动到第二张图片开始播放
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: flex; //父级盒子设置flex布局
transition-property: transform;
box-sizing: content-box;
}
.rotation-pagination {
position: absolute;
bottom: 3%;
width: 100%;
z-index: 10;
text-align: center;
.rotation-dot {
display: inline-block;
width: 8px;
height: 8px;
background-color: rgba(0, 0, 0, 0.3);
margin: 0 2px;
border-radius: 50%;
}
.rotation-dot:nth-child(1) {
background-color: var(--color-tint);
}
}
}
.rotation-slide {
flex-shrink: 0; //子元素flex-shrink:0表示子元素有自己的宽度,不需要flex自动计算子元素的宽度
width: 100%;
height: 100%;
position: relative;
transition-property: transform;
a {
display: block;
img {
width: 100%;
height: 95vw;
object-fit: cover;
vertical-align: bottom;
}
}
}
</style>