中铁二局第六工程有限公司
摘要:为了降低开发复杂度和减少前端开发工作量,屏蔽移动端和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属性