互联网前端应用一致性

(整期优先)网络出版时间:2018-12-22
/ 4

互联网前端应用一致性

马海

中铁二局第六工程有限公司

摘要:为了降低开发复杂度和减少前端开发工作量,屏蔽移动端和PC桌面端浏览器的细微差异,在一个开发框架中,构造一个通用底层结构,在DOM渲染、网络请求、鼠标按键及触屏响应等多个方面,分别支持各个常用浏览器,对外提供统一的API接口,基于这些API的业务逻辑代码,即可运行于各个浏览器,并得到足够的一致性,从而降低开发复杂度和减少开发工作量。

关键词:互联网前端应用,一致性,接口

Abstact:Inordertoreducedevelopmentcomplexity,theworkloadoffront-enddevelopmentandtoshieldthesubtledifferencesbetweenthemobileclientandPCdesktopbrowsersclient,agenericunderlyingstructureisconstructedtosupportthecommonbrowsersinmanyaspectssuchasDOMrendering,networkrequests,mouseeventsandtouchscreenresponseinadevelopmentframework.ThebusinesslogiccodebasedontheunifiedAPIinterfacecanruninvariousbrowsersandgetenoughconsistencytoreducedevelopmentcomplexityanddevelopmentworkload.

Keywords:Internetfront-endapplication,Consistency,Interface

前言

随着互联网+、云计算的普及,互联网前端应用(以下简称为:前端应用)越来越多,重要性也越来越受到重视。然而,在HTML5(以下简称为:H5)尚未普及的情况下,各个浏览器的兼容性是需要专门处理的。比如,IE8仍然是最为主要的浏览器(【1】),即使是IE11,其渲染内核也与Firefox、Chrome等浏览器内核不同;此外,各个浏览器对H5和CSS3的支持也不全面,存在一些细微区别。这就要求一款前端应用需要针对不同的浏览器开发独立的版本,导致开发复杂度提高,工作量增加。

为了较好地降低开发复杂度和减少开发工作量,本文尝试通过前端应用一致性来解决这个问题。

一.一致性概念

一致性(Consistency),是指应用在各个浏览器中的表现一致、操作一致、结果一致。

鉴于各个浏览器的特性,应用做到完全一致性,是有一定难度的;但是,做到大致的一致性,是可以实现的。

为此,本文提出了一个解决方案:构造一个通用底层结构,在DOM渲染、网络请求、鼠标按键及触屏响应等多个方面,分别支持各个常用浏览器,对外提供统一的API接口,实现只需要编写一套基于这些API的业务逻辑代码,即可运行于各个浏览器,并得到足够的一致性,从而降低开发复杂度和减少开发工作量。

这个实现方案如下图所示:

图1通用底层结构图示

此外,由于一致性好,开发出来的具体应用更容易得到用户的认可,比如:

●不需要让用户重新学习。

●不会误导用户。

在最初接触应用程序时,用户会通过一些教程或仅仅通过仔细地浏览学习来熟悉这款软件。这就是在相似的平台中鼓励更直观的可用性操作。当然,手势和导航控件也各不相同,但是桌面端和移动端的信息框架和架构框架应该是相似的。缺乏一致性将可能严重影响用户的体验。

二.一致性方案及实现

前端应用一致性涉及多个方面,本节将阐述具体的实现方案。

构造一个通用底层结构,是实现浏览器一致性的关键。由图1可知,这个通用底层结构大致包含如下功能:DOM渲染(分为盒子模型、响应式布局、动画三个方面)、网络请求、鼠标按键及触屏。

1.盒子模型

不同浏览器对于盒子模型(BoxModel)(【2】)有两种解释,分别为:content-box和border-box。两者的区别是:盒子的宽度是否包含边框(border)和内边距(padding)。

content-box,也称为W3C标准盒模型,即CSS样式声明的高度和宽度是content的尺寸,不包含padding和border,如下图所示:

图2:content-box图示

border-box,也称为IE盒子模型,即即CSS样式声明的高度和宽度是整体的尺寸,包含了padding和border,如下图所示:

图3:border-box图示

显然,这两种模式差别很大。会直接导致同一个css申明,在不同浏览器中显示结果不同。

为了解决这个问题,css3标准加入了box-sizing这个属性(【3】)。通过显式地设置来明确地让浏览器采用一种盒子模型进行DOM渲染。

一般地,border-box比content-box更为符合人们的常识,也更为友好。

所以,对于具体的应用,我们要求前端应用在设计时,对每一个具有静态大小要求的Element显式地设置box-sizing为border-box值。这样处理还有一个好处,就是同时适用于各个浏览器,无需单独针对一个浏览器再做处理。

2.响应式布局

现代的显示器分辨率非常多样,从移动端的320*480到桌面端的1920*1080,多达数十种。为了适应在不同的分辨率下均能得道满意的显示效果,我们专门提供了一整套具有响应式功能的布局(Layout)API接口,主要有onResize、onDock两个。

一.1.1.onResize

onResize接口主要实现切换分辨率、显示缩放、屏幕旋转等事件响应。

除去低于IE8的各个浏览器在切换分辨率、显示缩放、屏幕旋转时,均会向window发出resize事件,我们只需要响应这个resize事件,即可调用onResize接口,示例代码如下:

varoldBox=getWindowBox();

Viewport._rlh=on(window,"resize",function(){

varnewBox=getWindowBox();

if(oldBox.h===newBox.h&&oldBox.w===newBox.w){

return;

}

oldBox=newBox;

Viewport.emit("resize");

});

针对低于IE8的浏览器,需要单独处理。我们通过一个定时循环来检测,示例代码如下:

if(has("ie")<=8){

vardeviceXDPI=screen.deviceXDPI;

Viewport._ie8ScreenSizeHandle=setInterval(function(){

if(screen.deviceXDPI!==deviceXDPI){

deviceXDPI=screen.deviceXDPI;

Viewport.emit("resize");

}

},500);

}

通过这两项处理,我们可以完善地获取到显示模式的改变,并及时地做出相应的响应。

一.1.2.onDock

onDock主要用在布局控件(LayoutWidget)中。当布局控件接收到onResize调用时,会检测当前的DOM层级显示模式,根据其父节点的显示模式的变化而产生onDock,从而实现响应式布局。示例代码如下:

onDock:function(parentBox){

//parentBox为父节点的box

if(parentBox.w*2>parentBox.h*3){//判断父节点是否为横向显示

//如果父节点是横向显示,则向右停靠,即setDockRegionRight

if(this._dockRegion!=="right"){

this.setDockRegionRight();

}

}else{

//如果父节点是纵向显示,则向底部停靠,即setDockRegionBottom

if(this._dockRegion!=="bottom"){

this.setDockRegionBottom();

}

}

}

通过对onResize和onDock的综合处理,我们的前端应用即可做出完善的自适应布局响应。

3.动画

CSS3里面新增了transition属性(【4】),通过这个属性,可以较好地实现动画功能。

然而,并不是所有的浏览器都能够很好地支持transition属性,所以我们专门编写了动画API接口,自动判断浏览器是否能够使用transition属性。当浏览器能够较好地使用transition属性时,则直接通过transition属性产生动画,否则就通过接口模拟产生相应的动画。

所谓模拟产生动画,即是接口通过一个定时器来模拟transition属性,生成一系列动画帧来实现动画。示例代码如下:

_clearTimer:function(){

clearTimeout(this._delayTimer);

deletethis._delayTimer;

},

_startTimer:function(){

//启动定时器

varself=this;

if(!self._timer){

self._timer=after(runner,"run",function(){

self._cycle();//动画处理

},true);

countor++;

}

if(!timer){

timer=setInterval(function(){

runner.run();//

},self.rate);

}

},

_stopTimer:function(){

//停止定时器

if(this._timer){

this._timer.remove();

this._timer=null;

countor--;

}

if(countor<=0){

clearInterval(timer);

timer=null;

countor=0;

}

},

_cycle:function(){

//动画处理

varself=this,

curr,step;

if(self._active){

curr=newDate().valueOf();

step=self.duration===0?1:(curr-self._startTime)/(self.duration);//计算动画帧曲线值

if(step>=1){

step=1;

}

self._percent=step;

self._fire("_onAnimate",[self._getCurrentValue()]);//动画帧处理

if(self._percent<1){

self._startTimer();

}else{

self._fire("_onEnd",[]);

if(!self.repeat){

self._stopTimer();

}

}

}

returnself;

}

由上述代码可知:_onAnimate为实际的动画帧处理方法,具体的前端应用,只需要实现_onAnimate方法即可完全地模拟transition属性,从而很好地实现了各个浏览器的动画一致性。

4.网络请求

各个浏览器的网络请求(xhr)也是有差异的,我们为此构建了一个xhr对象,统一处理各个浏览器的网络请求。

首先,针对各个浏览器初始化xhr对象,示例代码如下:

if(has('native-xhr')){

//如果浏览器支持XMLHttpRequest,则直接使用XMLHttpRequest

xhr._create=function(){

returnnewXMLHttpRequest();

};

}elseif(has('activex')){

//如果浏览器支持activex,则分别尝试Msxml2.XMLHTTP或Microsoft.XMLHTTP

try{

newActiveXObject('Msxml2.XMLHTTP');

xhr._create=function(){

returnnewActiveXObject('Msxml2.XMLHTTP');

};

}catch(e){

try{

newActiveXObject('Microsoft.XMLHTTP');

xhr._create=function(){

returnnewActiveXObject('Microsoft.XMLHTTP');

};

}catch(e){}

}

}

接下来,则是实现xhr的四种请求模式,即:GET、POST、PUT、DELETE,并统一接口API,对外提供为四个API接口:

xhr.get=function(url,options){};

xhr.post=function(url,options){};

xhr.put=function(url,options){};

xhr.del=function(url,options){};

具体的前端应用都可以通过这四个API无差别的实现网络请求,从而保证了浏览器一致性。

5.鼠标按键及触屏

各个浏览器的鼠标和按键响应没有差异,但是对触屏的响应则略有差异;此外,如果能够将触屏和鼠标按键响应统一起来,则能够得到更好的一致性。

各个浏览器对触屏的响应差异主要体现为事件名的不同,IE系列浏览器以MSPointer作为事件名,其他浏览器以pointer作为事件名。这个比较好处理,示例代码如下:

functionpointerName(type){

returnhas("MSPointer")?

"MSPointer"+type.charAt(0).toUpperCase()+type.slice(1):

"pointer"+type;

}

为了能够将触屏和鼠标按键响应统一起来,我们构建了一个Touch对象,示例代码如下:

Touch={

click:function(node,listener){

//键鼠的click和触屏的click一样处理

returnon(node,"click",listener);

},

dblclick:function(node,listener){

//通过判断两次click的时间间隔,来模拟dblclick

returnon(node,"click",function(evt){

varnow=(newDate()).getTime();

if(now-_lastClick<500){

listener(evt);

}

_lastClick=now;

});

},

press:function(node,listener){

//通过mousedown和keydown来模拟press

vartouchListener=on(node,touch.press,function(evt){

if(evt.type==="mousedown"&&!mouse.isLeft(evt)){

//忽略右键click

return;

}

listener(evt);

}),keyListener=on(node,"keydown",function(evt){

if(evt.keyCode===keys.ENTER||evt.keyCode===keys.SPACE){

listener(evt);

}

});

return{

remove:function(){

touchListener.remove();

keyListener.remove();

}

};

},

release:function(node,listener){

//通过mouseup和keyup来模拟release

vartouchListener=on(node,touch.release,function(evt){

if(evt.type==="mouseup"&&!mouse.isLeft(evt)){

//忽略右键click

return;

}

listener(evt);

}),keyListener=on(node,"keyup",function(evt){

if(evt.keyCode===keys.ENTER||evt.keyCode===keys.SPACE){

listener(evt);

}

});

return{

remove:function(){

touchListener.remove();

keyListener.remove();

}

};

},

move:touch.move,

cancel:touch.cancel,

over:touch.over,

out:touch.out,

enter:touch.enter,

leave:touch.leave,

tap:gesture.tap,

hold:gesture.tap.hold,

doubletap:gesture.tap.doubletap,

swipe:gesture.swipe,

swipeend:gesture.swipe.end

};

如此处理后,具体前端应用中,只需要调用Touch即可统一响应鼠标按键和触屏的事件;即只需要编写一个事件响应方法,就可以无差别地响应鼠标按键或触屏,保证了应用的操作一致性。比如:

on(Touch.press,function(evt){

evt.preventDefault();

//这里是具体的处理代码

});

通过上面这段代码,即可以响应鼠标的mousedown,也可以响应按键的keydown,以及触屏的press。

6.桌面端与移动端一致性

实现了前面几项一致性功能后,我们不仅能够保证桌面端的浏览器一致性,甚至可以保证桌面端和移动端的浏览器一致性。

下面的图4和图5分别展示了桌面Chrome浏览器显示效果和Android默认浏览器显示效果。从这两幅图示可以看出,浏览器一致性相当满意。

图4:桌面Chrome浏览器显示效果图示

图5:Android默认浏览器显示效果图示

三.总结

本文提出了浏览器一致性概念,并阐述了浏览器一致性的解决方案,即:构造一个通用底层结构,在DOM渲染(分为盒子模型、响应式布局、动画三个方面)、网络请求、鼠标按键及触屏响应等多个方面,分别支持各个常用浏览器,对外提供统一的API接口,实现只需要编写一套基于这些API的业务逻辑代码,即可运行于各个浏览器,并得到足够的一致性,从而降低开发复杂度和减少开发工作量。

此解决方案,经过实际的前端应用,不仅实现了桌面端各个浏览器一致性,还实现了桌面端浏览器和移动端浏览器一致性,并获得了满意效果。

参考资料:

【1】:2016年网页浏览器发展现状分析

【2】:MDNWebDocs盒模型

【3】:W3schoolCSS参考手册CSS3box-sizing属性

【4】:W3schoolCSS参考手册CSS3transition属性