設計模式---狀態模式在web前端中的應用

在vue.js之類的mvvm的框架大行其道的當下,開發中最多見的場景就是經過改變數據來展現頁面或模塊的不一樣狀態,當咱們把mvvm玩的不亦樂乎的時候,有時也會停下了想一想:在某些項目中不能用vuejs之類的框架時,咱們怎麼經過改變數據來修改頁面或者模塊的狀態呢。
嗯。說到狀態,就想到了狀態模式javascript

狀態模式:css

在不少狀況下,一個對象的行爲取決於一個或多個動態變化的狀態屬性,這樣的對象叫作有狀態的(stateful)對象,對象的狀態是從事先定義好的一系列狀態值中取出的。當狀態對象與外部事件產生互動時,其內部狀態就會改變,從而使得系統的行爲也隨之發生變化。html

狀態模式主要解決的問題:前端

當控制對象狀態的條件表達式過於複雜時的狀況,把狀態的判斷邏輯轉移到表示不一樣的狀態的一系列類或者方法當中,能夠把複雜的邏輯判斷簡單化vue

下面咱們看看狀態模式在web前端開發中得一些常見應用場景java

tab標籤的切換

tab標籤切換是web開發中最多見的交互了,交互順序大體是這樣子:jquery

1.通常會有2-3個標籤,對應了2-3個tab內容塊。其中一個默認tab會是active狀態web

2.點擊其餘tab標籤,則:去掉active的tab標籤樣式,隱藏對應的區塊,給點擊的tab添加active樣式,顯示這個tab對應的區塊。promise

哎。。想一想須要挨個tab找對應的元素而後去remove掉class再add一個class。。。好繁瑣。。能不能少寫點js,簡簡單單的實現tab切換呢。。。框架

應用狀態模式解決tab切換示例以下:

html 代碼

<div class="tab-box-wrap" data-tab-index="tab01" id="tab_box_wrap">
    <ul>
        <li data-tab="tab01">我是tab01</li>
        <li data-tab="tab02">我是tab02</li>
        <li data-tab="tab03">我是tab03</li>
    </ul>
    <div tab-box="tab01">
        我是tab01 的內容
    </div>
    <div tab-box="tab01">
        我是tab02的內容
    </div>
   <div tab-box="tab01">
       我是tab03的內容
    </div>

</div>

css代碼

.tab-box-wrap {
   height: 500px;
   width: 900px;
   margin: 0 auto;
}

.tab-list {
   list-style: none;;
   height: 32px;
   border-bottom: 1px solid #6856f9;
   position: relative;
   padding: 0 50px;
   margin: 0;
}

.tab-list li {
   float: left;
   height: 20px;
   padding: 5px;
   border: 1px solid #6856f9;
   line-height: 20px;
   font-size: 14px;
   margin-right: -1px;
   border-bottom: 1px solid transparent;
   background: #fff;
   cursor: pointer;
}

[data-tab-index="tab01"] [tab-index="tab01"] {
   border-bottom: 2px solid #fff;
}

[data-tab-index="tab02"] [tab-index="tab02"] {
   border-bottom: 2px solid #fff;
}

[data-tab-index="tab03"] [tab-index="tab03"] {
   border-bottom: 2px solid #fff;
}

.tab-box {
   padding: 20px;
   height: 500px;
   border: 1px solid #6856f9;
   border-top: 0 none;
   display: none;
}

[data-tab-index="tab01"] [tab-box="tab01"] {
   display: block;
}

[data-tab-index="tab02"] [tab-box="tab02"] {
   display: block;
}

[data-tab-index="tab03"] [tab-box="tab03"] {
   display: block;
}

js代碼以下

var  tab_box_wrap = document.getElementById("tab_box_wrap");
   tab_box_wrap.addEventListener("click",function(event){
       var ele = event.target;
       var tab = ele.getAttribute("tab-index");
       if(tab){
           var t = tab_box_wrap.getAttribute("data-tab-index");
           if(t!==tab){
               tab_box_wrap.setAttribute("data-tab-index",tab);
           }
       }

   },false)

點擊查看demo

代碼不多,主要的代碼其實在css裏面, 咱們用屬性選擇器[data-tab-index="xxx"]來控制頁面的tab切換,js裏只須要獲取對應的tab點擊而後更改屬性的值就行了。

缺點:css有點多,須要枚舉每一個tab的狀態,css屬性選擇器ie8+才支持,

優勢:js代碼不多,邏輯控制全在css裏,有什麼改動 (好比添加tab) 咱們只須要改css就行了。一聽只改css,是否是很開森^_^

下面咱們看一個複雜的例子

global物流查詢

主要是物流詳情狀態的動畫,這個動畫會根據對應的運單號的狀態來展現對應的動畫節點。那麼咱們一共有10個運單狀態,以下:

{statusDesc: "攬收", status: "PICKEDUP",}
{statusDesc: "運輸中", status: "SHIPPING"},
{statusDesc: "離開發件國", status: "DEPART_FROM_ORIGINAL_COUNTRY"},
{statusDesc: "到達目的國", status: "ARRIVED_AT_DEST_COUNTRY"},
{statusDesc: "妥投", status: "SIGNIN"}
{statusDesc: "待攬收", status: "WAIT4PICKUP"},
{statusDesc: "到達待取", status: "WAIT4SIGNIN"}
{statusDesc: "妥投失敗", status: "SIGNIN_EXC"},
{statusDesc: "交航失敗", status: "DEPART_FROM_ORIGINAL_COUNTRY_EXC"},
{statusDesc: "清關失敗", status: "ARRIVED_AT_DEST_COUNTRY_EXC"}

無狀態的時候動畫狀態以下:
animate

妥投狀態動畫截圖以下:
animate

每個正常節點中間都會有一個異常節點,【到達目的國】和【妥投】之間會有一個【到達待取】狀態,
【攬收】和【離開發件國】之間會有一個【運輸中】狀態

下面咱們來思考咱們的代碼應該怎麼寫。。。

  • 無狀態灰色div裏放一個背景圖片,上面的文字是html節點。

  • 灰色模塊上面附一層紫色block,文字也是html節點,不一樣狀態設置不一樣的div的width就行了。

  • 狀態文字單獨是節點一共10個狀態文字節點

我指望改變一個div屬性整個動畫都會相應的變化

html代碼以下

<div img-animate="init" id="waybill_img_box" class="waybill-img-box" >
        <div class="waybill-img01"></div>
       <div id="waybill_img_color" class="waybill-img02 dot-scale"></div>
        <b class="waybill-no-color-01">攬收</b>
        <b class="waybill-no-color-02">離開發件國</b>
        <b class="waybill-no-color-03">到達目的國</b>
        <b class="waybill-no-color-04">妥投</b>
        <b class="waybill-color-01">攬收</b>
        <b class="waybill-color-02">離開發件國</b>
        <b class="waybill-color-03">到達目的國</b>
        <b class="waybill-color-04">妥投</b>
        <b class="waybill-color-05">到達待取</b>
        <b class="waybill-color-06">運輸中</b>
    </div>

咱們給#waybill_img_box 這個div添加一個img-animate的屬性,用來控制他的子節點的展現。

  • waybill-img01這個div用來放灰色的圖片

  • waybill_img_color 這個div放的是彩色圖片,咱們更改她的width來實現動畫,它是絕對定位的

  • 剩下的一堆b標籤都是文字內容的定位。固然他們也是絕對定位的

css代碼以下:爲了簡短,我只列舉了兩種狀態

/*初始化的時候全部的紫色模塊的字全都應該隱藏*/
 [img-animate="init"] .waybill-color-01,
 [img-animate="init"] .waybill-color-02, 
 [img-animate="init"] .waybill-color-03, 
 [img-animate="init"] .waybill-color-04,
 [img-animate="init"] .waybill-color-05, 
 [img-animate="init"] .waybill-color-06 {
    z-index: -1;
}
[img-animate="init"] .waybill-img02 {
    width: 0;
}
/*紫色元素的字的默認狀態*/
.waybill-color-01,
.waybill-color-02, 
.waybill-color-03,
.waybill-color-04,
.waybill-color-05,
.waybill-color-06 {
    text-align: center;
    font-weight: 500;
    position: absolute;
    top: 78px;
    left: 12px;
    z-index: 20;
    color: #fff;
    background: #4b4ebe;
    padding: 4px 5px;
    height: 12px;
    line-height: 12px;
    border-radius: 3px;
    display: none\9;
    -moz-transform: scale(0);
    -webkit-transform: scale(0);
    -o-transform: scale(0);
    -ms-transform: scale(0);
    transform: scale(0);
}

.waybill-color-02 {
    left: 208px;
    top: 28px;
    min-width: 35px;
}

.waybill-color-03 {
    left: 419px;
    top: 30px;
    min-width: 70px;
}

.waybill-color-04 {
    left: 636px;
    top: 75px;
    min-width: 50px;
}

.waybill-color-05 {
    left: 562px;
    top: 50px;
    min-width: 50px;
}

.waybill-color-06 {
    left: 105px;
    top: 42px;
    min-width: 50px;
}

/*
*下面寫攬收狀態的樣式 
*PICKEDUP 
*/
[img-animate="PICKEDUP"] .waybill-no-color-01,
[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-no-color-01 {
    -moz-transition: all 0.3s ease-in 0s;
    -webkit-transition: all 0.3s ease-in 0s;
    -o-transition: all 0.3s ease-in 0s;
    transition: all 0.3s ease-in 0s;
    -moz-transform: translate(8px, 95px) scale(0);
    -webkit-transform: translate(8px, 95px) scale(0);
    -o-transform: translate(8px, 95px) scale(0);
    -ms-transform: translate(8px, 95px) scale(0);
    transform: translate(8px, 95px) scale(0);
    }

[img-animate="PICKEDUP"] .waybill-color-01,
[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-no-color-02{
    -moz-transition: all 0.3s ease-in 0s;
    -webkit-transition: all 0.3s ease-in 0s;
    -o-transition: all 0.3s ease-in 0s;
    transition: all 0.3s ease-in 0s;
    -moz-transform: scale(1);
    -webkit-transform: scale(1);
    -o-transform: scale(1);
    -ms-transform: scale(1);
    transform: scale(1);
    z-index: 20;
    }
[img-animate="PICKEDUP"] .waybill-color-02, 
[img-animate="PICKEDUP"] .waybill-color-03, 
[img-animate="PICKEDUP"] .waybill-color-04, 
[img-animate="PICKEDUP"] .waybill-color-06, 
[img-animate="PICKEDUP"] .waybill-color-05 {
    z-index: -1;
}
/*寫一個離開發件國的狀態
* DEPART_FROM_ORIGINAL_COUNTRY
*/
[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-color-03,
[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-color-04{
    z-index: -1;
 }
 
[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-no-color-01{
    -moz-transform: translate(8px, 85px) scale(0);
    -webkit-transform: translate(8px, 85px) scale(0);
    -o-transform: translate(8px, 85px) scale(0);
    -ms-transform: translate(8px, 85px) scale(0);
    transform: translate(8px, 85px) scale(0);
}

[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-color-01{
    -moz-transition: all 0.3s ease-in 0s;
    -webkit-transition: all 0.3s ease-in 0s;
    -o-transition: all 0.3s ease-in 0s;
    transition: all 0.3s ease-in 0s;
    -moz-transform: scale(1);
    -webkit-transform: scale(1);
    -o-transform: scale(1);
    -ms-transform: scale(1);
    transform: scale(1);
    z-index: 20;
}

[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-no-color-02{
    -moz-transition: all 0.3s ease-in 0s;
    -webkit-transition: all 0.3s ease-in 0s;
    -o-transition: all 0.3s ease-in 0s;
    transition: all 0.3s ease-in 0s;
    -moz-transform: translate(8px, 20px) scale(0);
    -webkit-transform: translate(8px, 20px) scale(0);
    -o-transform: translate(8px, 20px) scale(0);
    -ms-transform: translate(8px, 20px) scale(0);
    transform: translate(8px, 20px) scale(0);
}

[img-animate="DEPART_FROM_ORIGINAL_COUNTRY"] .waybill-color-02{
    -moz-transition: all 0.3s ease-in 0s;
    -webkit-transition: all 0.3s ease-in 0s;
    -o-transition: all 0.3s ease-in 0s;
    transition: all 0.3s ease-in 0s;
    -moz-transform: scale(1);
    -webkit-transform: scale(1);
    -o-transform: scale(1);
    -ms-transform: scale(1);
    transform: scale(1);
    z-index: 20;
   }

大體思路和tab的實現是同樣的,咱們在css裏面枚舉了每一個狀態下每一個模塊應該對應的狀態。 咱們把c通用的css寫在一個樣式裏,其餘的每一個狀態只須要重置特定的樣式就能夠了

接下來看狀態模式在js裏的應用

js裏面咱們把狀態對應到每個函數裏面就行了。動畫使用了jquery的動畫。js代碼以下

var statusAnimateMap={
    PICKEDUP: function(callback){
        var self = waybillDetail;
        //更具對應的狀態設置動畫
            //var w = ONEWAYBILL?32:32;
            self.waybill_img_colorBox.animate(
                {
                    width:32
                },500,function(){
                    self.waybill_img_box.attr("img-animate",'PICKEDUP');
                    if(callback&&typeof callback==="function"){
                        callback();
                    }

                });

    },
    DEPART_FROM_ORIGINAL_COUNTRY: function(callback){
        var self = waybillDetail;
        //更具對應的狀態設置動畫
            var w = ONEWAYBILL?345:243;
            this.PICKEDUP(function(){
                self.waybill_img_colorBox.animate(
                    {
                        width:w
                    },800,function(){
                        self.waybill_img_box.attr("img-animate",'DEPART_FROM_ORIGINAL_COUNTRY');
                        if(callback&&typeof callback==="function"){
                            callback();
                        }
                    });
            });
    },
}
//使用的時候很簡單
function statusAnimate(status){
    if(status&& statusAnimateMap[status]){
        statusAnimateMap[status]();
    }
}
statusAnimate("PICKEDUP");

這裏用到了jquery的animate動畫。而且業務要求:

每一個狀態結束才能執行下一個狀態的動畫,好比DEPART_FROM_ORIGINAL_COUNTRY這個狀態就須要

1.先執行PICKEDUP的動畫

2.再執行DEPART_FROM_ORIGINAL_COUNTRY的動畫,

聽起來是否是很耳熟,嗯有點promise的感受。。額不過這麼一個簡單的場景固然不須要勞煩promise的大駕了。。。咱們給animate綁定一個回調方法就行了。

嗯,具體的實現見這個demo

這個頁面還有個單條查詢的詳情狀態,頁面結構會不同,左側列表會隱藏。。。。查看示例,圖片會拉長,也就是說每一個狀態都須要單獨寫一個單條的查詢的樣式。。。額還好咱們的狀態都寫在css裏咱們只須要給頁面加一個屬性[oneMailNo]而後重置一下每一個狀態下各個節點的位置就行了,js只須要修改一下waybill_img_color的寬度就行了。嗯改css的成本很低的。。bingo

以上就是狀態模式在實際開發中得應用,咱們結合了css html js 綜合應用狀態模式。能夠大大減小項目裏面的邏輯代碼。提升開發效率,剩下的時間能夠去和設計師美眉聊聊生活。。談談人生理想。。。。

詳見個人博客https://www.56way.com

相關文章
相關標籤/搜索