用狀態機寫輪播

UCCs發表於2018-05-21

剛剛的輪播用具體思維做,因為不知道它有哪幾種狀態,就一步步來做,等效果做出來後,哪幾種狀態,一目瞭然。下面就用抽象思維做一遍.

用抽象思維做

初始化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;
}

輪播狀態

先來看下這個輪播有那幾種狀態

  1. 圖片出現在視窗狀態,我用 current 表示
  2. 圖片離開視窗狀態,我用 leave 表示
  3. 圖片準備進入視窗狀態,我用 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. 初始化每張圖片的位置,圖片1 出現在當前視窗current,圖片2、圖片3 應該在視窗右邊待命,隨時準備進入視窗enter
  2. 當圖片1 離開視窗時leave,圖片2 進入視窗current
  3. 當上一步全部完成後,圖片1 應該進入右邊待命,等待著進入視窗
  4. 這裡主要絕對定位後,會觸發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中的移動方向即可,還可以根據需要加上一些酷炫的操作。

相關文章