目录
关于template外壳:
关于图片的自动切换的处理:
为什么要把第一张图片外的 li 克隆一份放到 li 列表的最后:
函数节流处理 :
关于template外壳:
创建一个div,用于内容的呈现,溢出的内容设置为隐藏;
div下创建一个内外边距都为0px的ul,ul的高与div一致,宽为(所要展现图片数+1)* (div的宽),(注:加1是因为将克隆第一张图片并放到最后,完善切换效果更);
ul下创建一个list-style为none的li,设置li的宽高跟外部的div大小一样,li包裹一个img,img的宽高拉满,铺满div。用v-for遍历存放在data中的图片数组pictures,生成多个li、img,设置li向左浮动,这样多张图片就能并排排列在一行上展示。可以取消div隐藏溢出内容的属性,这样便于观察和调试。
在div区域内的左侧和右侧分别创建一个img,分别展示向左向右的小图片,用户点击相应的小图片,则执行相应的切换。div的底部设置一行并排的小圆,存在唯一一个填充项并跟随图片的切换而切换,用实时唯一填充项指示当前所展示的图片的信息,当用户点击未被填充的小圆圈时,将自动切换展示指定的图片。
当用户操作鼠标在div范围内活动时,左右两侧小图标以及底部的圆圈图标将显现(v-show),并且可以点击(背景图片也可以点击,所以整个最外侧的div区域以内都是可点击区域),鼠标样式变为pointer。
轮播图组件<template></template>中的内容:
<template>
<!-- 最外层div -->
<div class="outest" @mouseover="mouseEnter = true" @mouseleave="mouseEnter = false" >
<!-- 向左img -->
<img class="icon _left" src="../img/向左.png" alt="向左" v-show="mouseEnter" @click="gotoLast()"/>
<!-- 主要图片 -->
<ul ref="myul">
<li v-for="e of pictures" :key="e.index" :ref="e.ref">
<img :src="e.src" :alt="e.alt" />
</li>
</ul>
<!-- 底部小圆圈的设置 -->
<div class="mydiv" v-show="mouseEnter">
<div class="empty-cicle" v-for="e of pictures" :key="e.index" ref="cicle" @click="gotoDirectly(e.index)" ></div>
</div>
<!-- 向右div -->
<img class="icon _right" src="../img/向右.png" alt="向右" v-show="mouseEnter" @click="gotoNext()"/>
</div>
</template>
css:
<style scoped>
.outest {
width: 600px;
height: 400px;
border: 2px rgb(115, 170, 243) solid;
border-radius: 5px;
padding: 0px;
position: absolute;
overflow: hidden;
cursor: pointer;
background-color: bisque;
}
/** 轮播图片设置 */
ul {
margin: 0px;
padding: 0px;
width: calc(6 * 100%);
height: 100%;
}
ul > li {
width: calc(1 / 6 * 100%);
height: 100%;
list-style: none;
float: left;
}
li > img {
width: 100%;
height: 100%;
}
/* 左右图标设置 */
.icon {
width: 30px;
height: 60px;
position: absolute;
top: 170px;
z-index: 2;
opacity: 0.5;
}
._left {
left: 0px;
}
._right {
right: 0px;
}
/* 底部小圆圈的设置 */
.mydiv {
width: auto;
height: 30px;
display: inline-block;
position: absolute;
bottom: 0px;
left: 250px;
z-index: 5;
opacity: 0.5;
}
.empty-cicle {
display: inline-block;
width: 10px;
height: 10px;
border: 2px solid black;
border-radius: 90px;
z-index: 5;
margin-right: 5px;
}
</style>
关于图片的自动切换的处理:
首先是当页面加载完毕后,要让图片的切换能自动进行。根据以上的html以及css配置,ul下每个li都位于一行显示,彼此紧凑,这样可以通过移动ul的绝对位置来改变最外层div(outest)中呈现的图片内容。先设置outset为绝对定位,然后在mounted中通过ref拿到dom来修改行内样式style,设置ul也为绝对定位,距离左侧为0px。后续将通过this.$refs.myul.style.offsetLeft获取myul左侧距离outset左侧的距离,通过改变this.$refs.myul.style.left来修改整个ul的位置,以实现图片过渡变换和改变呈现的图片。
可以在mounted配置对象中给window添加一个 一个自定义属性timer,然后赋一个定时器setInterval给他,让页面挂载完毕就开始执行定时器,定时器setInterval经过指定的时间自动执行,可以用于经过指定时间后自动切换图片。该定时器的回调函数中再嵌套一个setInterval,这个定时器则用来缓慢过渡到下一张图片。当用户操作鼠标进入到当前组件区域,可以触发一个自定义事件,清除定时器timer,让图片的变换停止。
我用了另外一种方式,在main.js文件 创建vm前给vm添加了一个自定义属性$mytimer,在组件文件中data设置一个属性mouseEnter,值为布尔值,当鼠标在组件区域内活动时改变其值为true,反之为false。并在watch中对它进行监视,开启立即监视immediate:true,立即监视使第一次执行handler()处理函数的时间在mounted之前,这样,当handler收到的第一个参数为false,将一个自动切换图片的定时器setInterval赋给$mytimer,这样页面一挂载就能自动执行图片的切换,并且每次鼠标离开后都会重新生成同样的定时器来切换图片。当handler收到的第一个参数为true时,清除$mytimer绑定的定时器。
main.js文件内容:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$mytimer=null
}
}).$mount('#app')
data中的数据(五张图片均来源于网络) :
data() {
return {
pictures: [
{src: "戴头盔",index: "3",ref: "picture",},
{src: "https://img1.baidu.com/it/u=3117634844,2583644829&fm=253&fmt=auto&app=138&f=JPEG?w=1024&h=492",alt: "培育文明之魂",index: "4",ref: "picture",},
],
mouseEnter: false, //鼠标是否悬浮在整个区域上
pictureShowing: 0, //当前展示的是第几张图片,用来更新底部圆点
hasClicked:false, //函数节流,防止用户快速、频繁点击图片变换导致出错,flase为图片变换操作不可执行
};
},
watch:
watch: {
mouseEnter: { //鼠标移入区域,图片切换停止,离开则继续
immediate: true,
handler(newval, oldval) { //使其在页面一上来就能执行
if (!newval) {
this.$mytimer = setInterval(() => {
if (this.pictureShowing < this.pictures.length-1) {
this.pictureShowing++;
} else {
this.pictureShowing = 0;
}
this.changeDefault(10);
}, 3000);
} else {
clearInterval(this.$mytimer);
}
},
},
pictureShowing(newval) { //当改变图片时,更新底部小圆圈
for (let i = 0; i < this.pictures.length; i++) {
(this.$refs.cicle)[i].style.background = "";
}
(this.$refs.cicle)[newval].style.background = "black";
},
},
mounted:
mounted() {
// console.log(this.$refs[`picture`]) //是一个 Array 包含 5 个 li 元素
// 解决当前页面被最小化后,用户再打开时,轮播图播放情况出现异常的情况
// 监视浏览器窗口的改变,当浏览器窗口最小化时,将默认定时器停掉,当浏览器可视时,再开启
document.addEventListener('visibilitychange',()=>{
if(document.visibilityState === 'hidden'){
clearInterval(this.$mytimer);
}else if(document.visibilityState === 'visible'){
this.$mytimer= setInterval(() => {
if (this.pictureShowing < this.pictures.length-1) {
this.pictureShowing++;
} else {
this.pictureShowing = 0;
}
this.changeDefault(10);
}, 3000);
}
})
let li_0 = this.$refs[`picture`][0];
let li_end = li_0.cloneNode(true);
this.$refs.myul.appendChild(li_end); //克隆第一张图片,加到ul最后展示的位置
this.$refs.myul.style.position = "absolute";
this.$refs.myul.style.left = 0 + "px";
this.$refs.cicle[0].style.background = "black"; //挂载时展示第一张图片,默认第一个圆圈被填充
},
为什么要把第一张图片外的 li 克隆一份放到 li 列表的最后:
(底部小圆圈也是通过遍历data中的pictures生成的,数量跟实际图片的个数一致,克隆出来放在最后的图片不会使圆圈个数增加)
整个轮播图设计下一张图片从右向左出现,上一张图片也从右向左隐藏(就像下一张图片从outest的右侧出来,把上一张图片从outest左侧挤走)。
根据当前组件的配置,以原始5张图片没有第六张克隆图片为例,当outest正在展示第五张图片,即将要自右向左退出outest区域进行隐藏时,如果ul整体往左移动,因为第五张往右就没有图片了,ul的宽度又恰好是五张图片的距离,此时右侧就会逐渐显示为空白(如果outest没有设置背景);如果说要在第五张向第一张过渡时,瞬间将ul的left设置为600(ul左侧距离outest区域左侧一个outset的宽度),让第一张图片自右往左显现,那么用户还是能看见左侧区域的空白(或outest的背景)。如果就把outest的背景就设置为第五张图片,并且采取上述的第二种方式,或许也能完成吧(快写完的时候想到的。。)。
克隆第一张图片的li,放到ul中li列表的末尾,主要为了解决的上述的第一种情况。当轮播到第五张图片,第五张图片即将要向左退出隐藏,因为第六张图片是第一张图片的克隆,所以第六张图片自右往左显现解决了原本第五张图片向第六张图片过渡的问题,当第六张图片恰好完全显示的瞬间,将ul的left设置为0px,将outest所展示的图片在刹那间替换为第一张,这样又回到了ul列表初始展示的位置,同时也解决了ul继续左移的问题。
methods:
methods: {
changeDefault(speed) { //切换下一张图片 speed为每毫秒移动的距离
let t = 0; //根据每秒移动的位移控制单次位移结束
let timer = setInterval(() => {
t++;
this.$refs.myul.style.left = this.$refs.myul.offsetLeft - Number(speed) + "px";
if (t === 600/speed) {
if(this.$refs.myul.offsetLeft===-3000){ //当最后一张图片(第一张的克隆)刚好完全展示时回到第一张完全展示时的位置
this.$refs.myul.style.left=0+"px";
}
clearInterval(timer);
this.hasClicked=false;
}
}, 1);
},
quickTurn(count,index) { //count为用户点击的点与当前 被填充点 的距离
let currentX = this.$refs.myul.offsetLeft;
let t = setInterval(() => {
this.$refs.myul.style.left = this.$refs.myul.offsetLeft - count * 20 + "px";
if (this.$refs.myul.offsetLeft === currentX - 600 * count) {
if(index===0){
this.$refs.myul.style.left=0+"px";
}
clearInterval(t);
this.hasClicked=false;
}
}, 1);
},
gotoPicture(index) {
let id = Number(index);
let showing = this.pictureShowing;
if (id === showing) {
return null;
} else {
this.quickTurn(index - showing,index);
this.pictureShowing = index;
}
},
gotoLast(){ //上一张
if(this.hasClicked){
return null;
}
this.hasClicked=true;
if(Number(this.pictureShowing)===0){
this.$refs.myul.style.left=-3000+"px";
this.pictureShowing=this.pictures.length-1;
this.quickTurn(-1,this.pictureShowing);
}else{
this.gotoPicture(this.pictureShowing-1);
}
},
gotoNext(){ //下一张
if(this.hasClicked){
return null;
}
this.hasClicked=true;
if(Number(this.pictureShowing)===this.pictures.length-1){
this.changeDefault(20);
this.pictureShowing=0;
}else{
this.gotoPicture(this.pictureShowing+1)
}
},
gotoDirectly(index){ //跳到某一张
if(this.hasClicked){
return null;
}
this.hasClicked=true;
this.gotoPicture(index);
}
},
函数节流处理 :
在data中定义的hasClicked,其值为布尔值,用于判断是否有定时器即将开始执行或正在执行。
为了防止用户快速地点击outset两侧的img或频繁更换点击底部的小圆而频频触发定时器,在左右的img和底部的小圆圈被点击时分别触发的函数gotoLast()、gotoNext()、gotoDirectly()中的开头都添加了一个判断,如果当前仍在执行上一个由gotoLast、gotoNext或gotoDirectly生成的定时器setInterval时(即this.hasClicked为true),则结束当前的函数进程(即当有定时器正在工作,则在此期间无法再触发函数以改变ul的位置)。当任意一个定时器运行结束时,将hasClicked设置为false,可以执行下一个触发定时器的操作。