用状态机写轮播
刚刚的轮播用具体思维做,因为不知道它有哪几种状态,就一步步来做,等效果做出来后,哪几种状态,一目了然。下面就用抽象思维做一遍.
用抽象思维做
初始化CSS样式
*{ margin:0; padding:0; box-sizing:border-box; } .window{ width:400px; height:300px; overflow:hidden; margin:20px auto } .images{ position:relative; } .images > img{ position:absolute; transition:all 0.5s; width:100%; top:0; }
轮播状态
先来看下这个轮播有那几种状态
- 图片出现在视窗状态,我用 current 表示
- 图片离开视窗状态,我用 leave 表示
- 图片准备进入视窗状态,我用 enter 表示
现在就是要写三个类,通过JS 激活class 来实现轮播
.images > img.current{ transform:translateX(0); z-index:1; } .images > img.leave{ transform:translateX(-100%); z-index:1; } .images > img.enter{ transform:translateX(100%); }
梳理下每张图片的状态
- 初始化每张图片的位置,图片1 出现在当前视窗
current
,图片2、图片3 应该在视窗右边待命,随时准备进入视窗enter
- 当图片1 离开视窗时
leave
,图片2 进入视窗current
- 当上一步全部完成后,图片1 应该进入右边待命,等待着进入视窗
- 这里主要绝对定位后,会触发BFC
$('#images > img:nth-child(1)').addClass('current'); $('#images > img:nth-child(2)').addClass('enter'); $('#images > img:nth-child(3)').addClass('enter'); setTimeout(function(){ $('#images > img:nth-child(1)').removeClass('current').addClass('leave').one('transitionend',function(e){ $(e.currentTarget).addClass('enter').removeClass('leave') }); $('#images > img:nth-child(2)').removeClass('enter').addClass('current') },3000); setTimeout(function(){ $('#images > img:nth-child(2)').removeClass('current').addClass('leave').one('transitionend',function(e){ $(e.currentTarget).addClass('enter').removeClass('leave') }); $('#images > img:nth-child(3)').removeClass('enter').addClass('current') },6000); setTimeout(function(){ $('#images > img:nth-child(3)').removeClass('current').addClass('leave').one('transitionend',function(e){ $(e.currentTarget).addClass('enter').removeClass('leave') }); $('#images > img:nth-child(1)').removeClass('enter').addClass('current') },9000);
这样一轮循环就结束了,可以在往后添加setTimeout
方法。
无限循环下去
大量重复的代码就需要寻找合适的的API 代替,一直播下去我们可以使用DOM APIsetInterval()
$('#images > img:nth-child(1)').addClass('current'); $('#images > img:nth-child(2)').addClass('enter'); $('#images > img:nth-child(3)').addClass('enter'); let n = 1; setInterval(function(){ $(`#images > img:nth-child(${x(n)})`).removeClass('current').addClass('leave').one('transitionend',function(e){ $(e.currentTarget).addClass('enter').removeClass('leave') }); $(`#images > img:nth-child(${x(n+1)})`).removeClass('enter').addClass('current') n++; //这里n 是自然增长,让它一直玄幻下去 },3000) //n取值应该是[1,2,3,4,5,...,size] let allImages = $('#images > img'); let size = allImages.length; function x(n){ if(n > size){ //如果n 大于节点size,n就取余 n = n%size; if(n === 0){ //如果n 取余为0,则让n等于size n = size; } } return n; }
这样就是实现了无缝轮播,上面用到了ES6的插值法。
在CSS中img:nth-child(n)
是没有这种写法的,它不能像JS一样可以用变量,这边就用到了ES6 的插值法
`img:nth-child(${n})`\
最后优化下刚刚写的代码
<style> *{ margin:0; padding:0; box-sizing:border-box; } .window{ width:400px; height:300px; overflow:hidden; margin:20px auto } .images{ position:relative; } .images > img{ position:absolute; transition:all 0.5s; width:100%; top:0; } <style> <div class="window"> <div id="images" class="images"> <img class='png1' src="./images/1.png" width=400 alt=""> <img class='png2' src="./images/2.png" width=400 alt=""> <img class='png3' src="./images/3.png" width=400 alt=""> <img class='png4' src="./images/4.png" width=400 alt=""> <img class='png5' src="./images/5.png" width=400 alt=""> </div> </div> <script> let n = 1; int(); setInterval(function(){ makeLeave(getImage(n)).one('transitionend',function(e){ makeEnter($(e.currentTarget)) }); makeCurrent(getImage(n+1)); n++; //这里n 是自然增长,让它一直循环下去 },1000); //n取值应该是[1,2,3,4,5,...,size] let allImages = $('#images > img'); let size = allImages.length; function x(n){ if(n > size){ //如果n 大于节点size,n就取余 n = n%size; if(n === 0){ //如果n 取余为0,则让n等于size n = size; } } return n; } function getImage(n){ return $(`#images > img:nth-child(${x(n)})`) } function int(){ $(`#images > img:nth-child(${n})`).addClass('current').siblings().addClass('enter'); } function makeLeave($node){ return $node.removeClass('current enter').addClass('leave') } function makeCurrent($node){ return $node.removeClass('enter leave').addClass('current') } function makeEnter($node){ return $node.addClass('enter').removeClass('leave current') } </script>
优化完了之后,实际代码就只有这么多,这个被称为状态机,现在再看轮播后,脑海里已经自动变成了状态机了。
let n = 1; int(); setInterval(function(){ makeLeave(getImage(n)).one('transitionend',function(e){ makeEnter($(e.currentTarget)) }); makeCurrent(getImage(n+1)); n++; //这里n 是自然增长,让它一直循环下去 },1000);
这里我遇到一个最大的问题之前,是用setTimeout()
方法写的代码,后面做无限循环时没想到用setInterval()
方法,怎么调试都不对,这里看下它们两个的区别:setTimeout()
方法设置一个定时器,在到时间后执行一段代码或者函数setInterval()
方法是重复调用一段代码或者函数,每次调用之间有固定的时间延时
我们上面写在setInterval()
方法内的函数其实就是一段固定的代码,每个一段时间执行一次,就变成我们看到的轮播了
总结
这一篇的核心是状态机,把动作变成一个个状态。用具体化写出的代码都是执行的动作,而用抽象化写出的代码都是完成后的状态,代码结构更清晰,更美观。当然要能写抽象化的代码,肯定少不了具体化的思维。
用这种方法最大的好处是行为样式分离,如果我要给变轮播的方向,只需要改变CSS中的移动方向即可,还可以根据需要加上一些酷炫的操作。