在vue.js之类的mvvm的框架大行其道的当下,开发中最多见的场景就是经过改变数据来展现页面或模块的不一样状态,当咱们把mvvm玩的不亦乐乎的时候,有时也会停下了想一想:在某些项目中不能用vuejs之类的框架时,咱们怎么经过改变数据来修改页面或者模块的状态呢。
嗯。说到状态,就想到了状态模式javascript
状态模式:css
在不少状况下,一个对象的行为取决于一个或多个动态变化的状态属性,这样的对象叫作有状态的(stateful)对象,对象的状态是从事先定义好的一系列状态值中取出的。当状态对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。html
状态模式主要解决的问题:前端
当控制对象状态的条件表达式过于复杂时的状况,把状态的判断逻辑转移到表示不一样的状态的一系列类或者方法当中,能够把复杂的逻辑判断简单化vue
下面咱们看看状态模式在web前端开发中得一些常见应用场景java
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,是否是很开森^_^
主要是物流详情状态的动画,这个动画会根据对应的运单号的状态来展现对应的动画节点。那么咱们一共有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代码以下
<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