资讯专栏INFORMATION COLUMN

设计模式---状态模式在web前端中的应用

刘东 / 2220人阅读

摘要:以上就是状态模式在实际开发中得应用,我们结合了综合应用状态模式。

在vue.js之类的mvvm的框架大行其道的当下,开发中最常见的场景就是通过改变数据来展示页面或模块的不同状态,当我们把mvvm玩的不亦乐乎的时候,有时也会停下了想想:在某些项目中不能用vuejs之类的框架时,我们怎么通过改变数据来修改页面或者模块的状态呢。
嗯。说到状态,就想到了状态模式

状态模式:

在很多情况下,一个对象的行为取决于一个或多个动态变化的状态属性,这样的对象叫做有状态的(stateful)对象,对象的状态是从事先定义好的一系列状态值中取出的。当状态对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。

状态模式主要解决的问题:

当控制对象状态的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同的状态的一系列类或者方法当中,可以把复杂的逻辑判断简单化

下面我们看看状态模式在web前端开发中得一些常见应用场景

tab标签的切换

tab标签切换是web开发中最常见的交互了,交互顺序大致是这样子:

1.一般会有2-3个标签,对应了2-3个tab内容块。其中一个默认tab会是active状态

2.点击其他tab标签,则:去掉active的tab标签样式,隐藏对应的区块,给点击的tab添加active样式,显示这个tab对应的区块。

哎。。想想需要挨个tab找对应的元素然后去remove掉class再add一个class。。。好繁琐。。能不能少写点js,简简单单的实现tab切换呢。。。

应用状态模式解决tab切换示例如下:

html 代码

  • 我是tab01
  • 我是tab02
  • 我是tab03
我是tab01 的内容
我是tab02的内容
我是tab03的内容

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"}

无状态的时候动画状态如下:

妥投状态动画截图如下:

每一个正常节点中间都会有一个异常节点,【到达目的国】和【妥投】之间会有一个【到达待取】状态,
【揽收】和【离开发件国】之间会有一个【运输中】状态

下面我们来思考我们的代码应该怎么写。。。

无状态灰色div里放一个背景图片,上面的文字是html节点。

灰色模块上面附一层紫色block,文字也是html节点,不同状态设置不同的div的width就好了。

状态文字多带带是节点一共10个状态文字节点

我期望改变一个div属性整个动画都会相应的变化

html代码如下

    
揽收 离开发件国 到达目的国 妥投 揽收 离开发件国 到达目的国 妥投 到达待取 运输中

我们给#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: none9;
    -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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86449.html

相关文章

  • 前端每周清单半年盘点之 React 与 ReactNative 篇

    摘要:前端每周清单半年盘点之与篇前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。与求同存异近日,宣布将的构建工具由迁移到,引发了很多开发者的讨论。 前端每周清单半年盘点之 React 与 ReactNative 篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为...

    Barry_Ng 评论0 收藏0
  • 前端练级攻略(第二部分)

    摘要:是文档的一种表示结构。这些任务大部分都是基于它。这个实践的重点是把你在前端练级攻略第部分中学到的一些东西和结合起来。一旦你进入框架部分,你将更好地理解并使用它们。到目前为止,你一直在使用进行操作。它是在前端系统像今天这样复杂之前编写的。 本文是 前端练级攻略 第二部分,第一部分请看下面: 前端练级攻略(第一部分) 在第二部分,我们将重点学习 JavaScript 作为一种独立的语言,如...

    BWrong 评论0 收藏0
  • 前端每周清单第 51 期: React Context API 与模式变迁, Webpack 与 W

    摘要:前端每周清单第期与模式变迁与优化界面生成作者王下邀月熊编辑徐川前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。 showImg(https://segmentfault.com/img/remote/1460000013279448); 前端每周清单第 51 期: React Context A...

    Jiavan 评论0 收藏0
  • 前端每周清单半年盘点之 Angular 篇

    摘要:延伸阅读学习与实践资料索引与前端工程化实践前端每周清单半年盘点之篇前端每周清单半年盘点之与篇前端每周清单半年盘点之篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎关注【前端之巅】微信公众号(ID:frontshow),及时获取前端每周清单;本文则是对于半年来发布的前端每周清单...

    LeviDing 评论0 收藏0
  • 前端进阶资源整理

    摘要:前端进阶进阶构建项目一配置最佳实践状态管理之痛点分析与改良开发中所谓状态浅析从时间旅行的乌托邦,看状态管理的设计误区使用更好地处理数据爱彼迎房源详情页中的性能优化从零开始,在中构建时间旅行式调试用轻松管理复杂状态如何把业务逻辑这个故事讲好和 前端进阶 webpack webpack进阶构建项目(一) Webpack 4 配置最佳实践 react Redux状态管理之痛点、分析与...

    BlackMass 评论0 收藏0

发表评论

0条评论

刘东

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<