响应式设计的意义

随着移动设备的发展,移动设备以迅猛的势头分刮着PC的占有率,ipad或者android pad的市场占有率稳步提升,所以我们的程序需要在ipad上很好的运行,对于公司来说有以下负担:设备系统上来说主要分为android ios;尺寸上看又以手机与pad为一个分界线,如果再加一个H5站点,其开发所投入资源不可谓不小!

Hybrid的出现,解决了大部分问题,针对尺寸上的问题有一种东西叫做响应式设计,这个响应式设计似乎可以解决我们的问题,所以今天我就来告诉大家什么是响应式设计,或者说我这种外行以为的响应式设计。

响应式Web设计(Responsive Web design)的理念是:集中创建页面的图片排版大小,可以智能地根据用户行为以及使用的设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相对应的布局。

以我粗浅的理解,响应式的提出,其实就是单纯的根据不同的尺寸,以最优的展示方式呈现罢了,仅仅而已,不能再多了,如果真要更多点,便是根据不同的尺寸对静态资源加载上有所控制,节约流量,换句话说,响应式设计不涉及业务逻辑,jser神马都不需要做,css同事便可完全解决,但事实上最近碰到的需求完全不是这么回事嘛。

以最简单图片轮播来说,手机上是这个样子的:

而在ipad横屏上,却变成了这个样子了:

我当时就醉了,iPad竖着保持手机样式,横着iPad样式,什么CSS有这么伟大,可以完成这个功能,而真实的场景是这个样子的:

手机端:首页搜索页->list页面->详情页->预定页

但是到了ipad横屏上:首页左屏是搜索页,右边是日期选择/城市选择/......,然后到了list页面,左边是list,右边是详情页,单击左边的list左边详情直接变化!

其实单独页面做的话,好像没有什么问题,但是手机业务早已铺开了,老板的意思是,代码要重用,还是全局改改CSS实现就好啦,我当时真为我们的UED捏了一把汗。到了具体业务实现的同事那里情况又变了,UED只是给出了两个设计好了的静态html+css,要怎么玩还得那个业务同事自己搞。

那天我去支援时,看到了其牛逼的实现,不由的菊花一紧,里面媒体查询都没有用,直接display: none 搞定一切问题了,这个对手机程序带来了很大的负担:原来一个view就是用于手机,现在无端的在里面加入了大量的pad端程序,直接造成了两个结果:

① 业务逻辑变得复杂,容易出BUG

② js尺寸变大,对手机端来说,流量很宝贵

虽然知道他那种做法不可取,当时忙于其它事情,并且天意难违,天意难测也只有听之任之,但是这里要说一点,响应式布局不太适合业务复杂的webapp,各位要慎重!

ipad版本应该怎么做?

虽然如此,问题还是需要解决,并且需要在框架层做出解决,这类需求本不应强加与CSS,好在曾经我们业务层的View设计基本是满足条件的,现在只需要扩展即可,仍然以blade框架为例:

每个页面片完成的工作仅仅依赖了一个View类,既然View是类,那么继承mobile的View,实现ipad的View,似乎是可能的,这一切的基石便是继承

继承的意义

我们这里的View Controller index.js开始是不完全满足我们的需求的,我们做一些调整,这里是调整前的代码:

 define(['View', getViewTemplatePath('index'), 'UIGroupList'], function (View, viewhtml, UIGroupList) {

   return _.inherit(View, {
onCreate: function () {
this.$el.html(viewhtml);
this.initElement(); this.TXTTIMERRES = null; }, initElement: function () {
this.cancelBtn = this.$('.cui-btn-cancle');
this.searchBox = this.$('.cui-input-box');
this.txtWrapper = this.$('.cui-citys-hd');
this.searchList = this.$('.seach-list'); }, events: {
'focus .cui-input-box': 'seachTxtFocus',
'click .cui-btn-cancle': function () {
this.closeSearch();
},
'click .seach-list>li': function (e) {
var gindex = $(e.currentTarget).attr('data-group');
var index = $(e.currentTarget).attr('data-index'); this.forward(this.uidata[gindex].data[index].uiname);
}
}, seachTxtFocus: function (e) {
this.openSeach();
}, closeSearch: function () {
this.txtWrapper.removeClass('cui-input-focus');
this.groupList.show();
this.searchList.hide();
this.searchBox.val('');
}, //开启搜索状态
openSeach: function () {
if (this.TXTTIMERRES) return; this.TXTTIMERRES = setInterval($.proxy(function () {
// console.log(1);
//如果当前获取焦点的不是input元素的话便清除定时器
if (!this.isInputFocus()) {
if (this.TXTTIMERRES) {
clearInterval(this.TXTTIMERRES);
this.TXTTIMERRES = null;
}
} var txt = this.searchBox.val().toLowerCase();
if (txt == '') {
setTimeout($.proxy(function () {
if (!this.isInputFocus()) {
this.closeSearch();
}
}, this), 500);
return;
} this.txtWrapper.addClass('cui-input-focus');
this.groupList.hide();
this.searchList.show(); var list = this.groupList.getFilterList(txt);
this.searchList.html(list); }, this)); }, isInputFocus: function () {
if (document.activeElement.nodeName == 'INPUT' && document.activeElement.type == 'text')
return true;
return false;
}, initGoupList: function () {
if (this.groupList) return;
var scope = this; //提示类
var groupList1 = [
{ 'uiname': 'alert', 'name': '警告框' },
{ 'uiname': 'toast', 'name': 'toast框' },
{ 'uiname': 'reloading', 'name': 'loading框' },
{ 'uiname': 'bubble.layer', 'name': '气泡框提示' },
{ 'uiname': 'warning404', 'name': '404提醒' },
{ 'uiname': 'layerlist', 'name': '弹出层list' }
]; var groupList2 = [ { 'uiname': 'identity', 'name': '身份证键盘' },
{ 'uiname': 'imageslider', 'name': '图片轮播' },
{ 'uiname': 'num', 'name': '数字组件' },
{ 'uiname': 'select', 'name': 'select组件' },
{ 'uiname': 'switch', 'name': 'switch组件' },
{ 'uiname': 'tab', 'name': 'tab组件' },
{ 'uiname': 'calendar', 'name': '日历组件' },
{ 'uiname': 'group.list', 'name': '分组列表' },
{ 'uiname': 'group.list', 'name': '搜索列表(城市搜索,地址搜索,待补充)' }
]; var groupList3 = [
{ 'uiname': 'radio.list', 'name': '单列表选择组件' },
{ 'uiname': 'scroll.layer', 'name': '滚动层组件(可定制化弹出层,比较常用)' },
{ 'uiname': 'group.select', 'name': '日期选择类组件' },
{ 'uiname': 'scroll', 'name': '滚动组件/横向滚动' },
]; var groupList4 = [
{ 'uiname': 'lazyload', 'name': '图片延迟加载' },
{ 'uiname': 'inputclear', 'name': '带删除按钮的文本框(todo...)' },
{ 'uiname': 'validate1', 'name': '工具类表单验证' },
{ 'uiname': 'validate2', 'name': '集成表单验证(todo...)' },
{ 'uiname': 'filp', 'name': '简单flip手势工具' }
]; var uidata = [
{ name: '弹出层类组件', data: groupList1 },
{ name: '常用组件', data: groupList2 },
{ name: '滚动类组件', data: groupList3 },
{ name: '全局类', data: groupList4 }
]; this.uidata = uidata; this.groupList = new UIGroupList({
datamodel: {
data: uidata,
filter: 'uiname,name'
},
wrapper: this.$('.cui-citys-bd'),
onItemClick: function (item, groupIndex, index, e) {
scope.forward(item.uiname);
}
}); this.groupList.show(); }, onPreShow: function () {
this.turning();
}, onShow: function () {
this.initGoupList();
}, onHide: function () { } });
});

调整前的代码

 <div id="headerview" style="height: 48px;">
<header>
<h1>
UI组件demo列表</h1>
</header></div> <section class="cui-citys-hd ">
<div class="cui-input-bd">
<input type="text" class="cui-input-box" placeholder="中文/拼音/首字母">
</div>
<button type="button" class="cui-btn-cancle">取消</button>
</section>
<ul class="cui-city-associate seach-list"></ul> <section class="cui-citys-bd">
</section>

对应HTML模板

调整后的代码如下:

 define(['View', getViewTemplatePath('index'), 'UIGroupList'], function (View, viewhtml, UIGroupList) {

   return _.inherit(View, {
onCreate: function () {
this.$el.html(viewhtml);
this.initElement(); this.TXTTIMERRES = null; }, initElement: function () {
this.cancelBtn = this.$('.cui-btn-cancle');
this.searchBox = this.$('.cui-input-box');
this.txtWrapper = this.$('.cui-citys-hd');
this.searchList = this.$('.seach-list'); }, events: {
'focus .cui-input-box': 'seachTxtFocus',
'click .cui-btn-cancle': 'closeSearchAction',
'click .seach-list>li': 'searchItemAction'
}, searchItemAction: function (e) {
var gindex = $(e.currentTarget).attr('data-group');
var index = $(e.currentTarget).attr('data-index');
this.forward(this.uidata[gindex].data[index].uiname);
}, closeSearchAction: function () {
this.closeSearch();
}, demoItemAction: function (item, groupIndex, index, e) {
scope.forward(item.uiname);
}, seachTxtFocus: function (e) {
this.openSeach();
}, closeSearch: function () {
this.txtWrapper.removeClass('cui-input-focus');
this.groupList.show();
this.searchList.hide();
this.searchBox.val('');
}, //开启搜索状态
openSeach: function () {
if (this.TXTTIMERRES) return; this.TXTTIMERRES = setInterval($.proxy(function () {
// console.log(1);
//如果当前获取焦点的不是input元素的话便清除定时器
if (!this.isInputFocus()) {
if (this.TXTTIMERRES) {
clearInterval(this.TXTTIMERRES);
this.TXTTIMERRES = null;
}
} var txt = this.searchBox.val().toLowerCase();
if (txt == '') {
setTimeout($.proxy(function () {
if (!this.isInputFocus()) {
this.closeSearch();
}
}, this), 500);
return;
} this.txtWrapper.addClass('cui-input-focus');
this.groupList.hide();
this.searchList.show(); var list = this.groupList.getFilterList(txt);
this.searchList.html(list); }, this)); }, isInputFocus: function () {
if (document.activeElement.nodeName == 'INPUT' && document.activeElement.type == 'text')
return true;
return false;
}, initGoupList: function () {
if (this.groupList) return;
var scope = this; //提示类
var groupList1 = [
{ 'uiname': 'alert', 'name': '警告框' },
{ 'uiname': 'toast', 'name': 'toast框' },
{ 'uiname': 'reloading', 'name': 'loading框' },
{ 'uiname': 'bubble.layer', 'name': '气泡框提示' },
{ 'uiname': 'warning404', 'name': '404提醒' },
{ 'uiname': 'layerlist', 'name': '弹出层list' }
]; var groupList2 = [ { 'uiname': 'identity', 'name': '身份证键盘' },
{ 'uiname': 'imageslider', 'name': '图片轮播' },
{ 'uiname': 'num', 'name': '数字组件' },
{ 'uiname': 'select', 'name': 'select组件' },
{ 'uiname': 'switch', 'name': 'switch组件' },
{ 'uiname': 'tab', 'name': 'tab组件' },
{ 'uiname': 'calendar', 'name': '日历组件' },
{ 'uiname': 'group.list', 'name': '分组列表' },
{ 'uiname': 'group.list', 'name': '搜索列表(城市搜索,地址搜索,待补充)' }
]; var groupList3 = [
{ 'uiname': 'radio.list', 'name': '单列表选择组件' },
{ 'uiname': 'scroll.layer', 'name': '滚动层组件(可定制化弹出层,比较常用)' },
{ 'uiname': 'group.select', 'name': '日期选择类组件' },
{ 'uiname': 'scroll', 'name': '滚动组件/横向滚动' },
]; var groupList4 = [
{ 'uiname': 'lazyload', 'name': '图片延迟加载' },
{ 'uiname': 'inputclear', 'name': '带删除按钮的文本框(todo...)' },
{ 'uiname': 'validate1', 'name': '工具类表单验证' },
{ 'uiname': 'validate2', 'name': '集成表单验证(todo...)' },
{ 'uiname': 'filp', 'name': '简单flip手势工具' }
]; var uidata = [
{ name: '弹出层类组件', data: groupList1 },
{ name: '常用组件', data: groupList2 },
{ name: '滚动类组件', data: groupList3 },
{ name: '全局类', data: groupList4 }
]; this.uidata = uidata; this.groupList = new UIGroupList({
datamodel: {
data: uidata,
filter: 'uiname,name'
},
wrapper: this.$('.cui-citys-bd'),
onItemClick: function (item, groupIndex, index, e) {
scope.demoItemAction(item.uiname);
}
}); this.groupList.show();
}, onPreShow: function () {
this.turning();
}, onShow: function () {
this.initGoupList();
}, onHide: function () { } });
});

PS:上面的代码是我几个月前写的,今天一看又觉得可以优化,当真优化无极限啊!!!

变化的关键点是每次我点击的事件全部放到了Index这个类的prototype上:

 searchItemAction: function (e) {
var gindex = $(e.currentTarget).attr('data-group');
var index = $(e.currentTarget).attr('data-index');
this.forward(this.uidata[gindex].data[index].uiname);
}, closeSearchAction: function () {
this.closeSearch();
}, demoItemAction: function (item, groupIndex, index, e) {
 scope.demoItemAction(item, groupIndex, index, e);
},

这里粒度到哪个程度与具体业务相关,我这里不做论述,于是我这里继承至index产生一个新的index类:index.ipad.js,这个是其基本实现:

 define([getViewClass('index'), getViewTemplatePath('index'), 'UIGroupList'], function (View, viewhtml, UIGroupList) {
return _.inherit(View, { onCreate: function ($super) {
$super();
}, onPreShow: function ($super) {
$super();
this.turning();
}, onShow: function ($super) {
$super();
this.initGoupList();
}, onHide: function ($super) {
$super();
}, events: { }, searchItemAction: function (e) {
var gindex = $(e.currentTarget).attr('data-group');
var index = $(e.currentTarget).attr('data-index');
this.forward(this.uidata[gindex].data[index].uiname);
}, demoItemAction: function (item, groupIndex, index, e) {
scope.forward(item.uiname);
} });
});

这个时候直接运行blade/ipad/debug.html#index.ipad的话,页面与原来index保持一致:

第二步便是重写其事件的关键位置了,比如要跳出的两个事件点:

 searchItemAction: function (e) {
var gindex = $(e.currentTarget).attr('data-group');
var index = $(e.currentTarget).attr('data-index');
this.forward(this.uidata[gindex].data[index].uiname);
}, demoItemAction: function (item, groupIndex, index, e) {
scope.forward(item.uiname);
} //简单改变 searchItemAction: function (e) {
var gindex = $(e.currentTarget).attr('data-group');
var index = $(e.currentTarget).attr('data-index');
alert(this.uidata[gindex].data[index].uiname);
}, demoItemAction: function (item, groupIndex, index, e) {
alert(item.uiname);
}

这个时候原版本的跳转,变成了alert:

这个时候便需要进一步重写了,比如这里:我点击alert,事实上是想在右边加载那个子view,所以框架全局控制器APP需要新增loadSubView的接口了:

新增接口

loadSubView要实现实例化某一View非常简单,但是该接口的工作并不轻松,换句话说会非常复杂,因为:

History与路由归一化是mobile与pad版本整合的难点

mobile的view与ipadview是公用的,所以本身不存在主次关系,是业务给予了其主次,这里需要一个管理关系

子View的实例化会涉及到复杂的History与路由管理,我们这里先绕过去,下个阶段再处理,因为完成pad版本,框架的MVC核心要经过一次重构

 //这里暂时不处理History逻辑,也不管子View的管理,先单纯实现功能
//这样会导致back的错乱,View重复实例化,这里先不予关注
loadSubView: function (viewId, wrapper, callback) { //子View要在哪里显示需要处理
if (!wrapper[0]) return; this.loadView(viewId, function (View) { var curView = new View(this, viewId, wrapper); //这个是唯一需要改变的
curView.turning = $.proxy(function () {
curView.show();
curView.$el.show();
}, this);
curView.onPreShow();
callback && callback(curView); }); },

在样式上再做一点调整就变成这个样子了:

这里History管理还是乱的,但是整个这个方案是可行的,所以我们前哨战是成功的,方案可行的话便需要详细的设计了

结语

今天,我们对ipad与mobile统一使用一套view代码做了研究,有以下收获与问题:

① 继承可实现ipad与mobile代码复用,并且不会彼此污染,至少不会污染mobile程序

② pad版本中History与路由管理需要重构

③ MVC需要重构,特别是View一块,甚至需要完全重新写

④ 样式方面还需要处理优化

总而言之,今天的收获还是有的,剩下的问题,需要在核心框架上动大动作了,最后的目标是能够出一套同用于ipad与mobile的框架。

源码:

https://github.com/yexiaochai/blade

demo在此:

http://yexiaochai.github.io/blade/ipad/debug.html#index.ipad

ipad&mobile通用webapp框架前哨战的更多相关文章

  1. webapp框架集合

    1.GoAngualrjs homepage  github GoAngular 可让你轻松使用 AngularJS 和 GoInstant 构建实时.多用户的应用程序. 2.JingleV home ...

  2. query通用开源框架

    Jquery通用开源框架之[ejq.js] 简介 ejq是一款非常小巧的JS工具库,未压缩才50K,在jquery的基础上对jquery缺失部分作了很好的弥补作用. 优点: 1.具有内置的模板解析引擎 ...

  3. 60.Android通用流行框架大全

    转载:https://segmentfault.com/a/1190000005073746 Android通用流行框架大全 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的 ...

  4. Razor 在WebApp 框架的运用

    前面有两章介绍了WebApp框架<WebApp MVC,“不一样”的轻量级互联网应用程序开发框架>和<WebApp MVC 框架的开发细节归纳>,其中视图引擎是用的Nveloc ...

  5. 看过《大湿教我写.net通用权限框架(1)之菜单导航篇》之后发生的事(续)——主界面

    引言 在UML系列学习中的小插曲:看过<大湿教我写.net通用权限框架(1)之菜单导航篇>之后发生的事 在上篇中只拿登录界面练练手,不把主界面抠出来,实在难受,严重的强迫症啊.之前一直在总 ...

  6. jQuery Mobile与QUI框架的异曲同工之处

    最近一直在研究jQuery Mobile框架,这是jQuery的官方移动版UI框架,专门用来开发手机与平板电脑方面的应用.结果越来越觉得它和我的QUI框架的开发思路非常相似,很多地方都有异曲同工之妙. ...

  7. 【开源推荐】AllJoyn:打造全球物联网的通用开源框架

    摘要:随着智能设备的发展,物联网逐渐进入了人们的生活.据预测,未来几乎一切东西(超过500亿台设备)都可以互联.高通公司发布了开源项目AllJoyn,这是一个能够使连接设备间进行互操作的通用软件框架和 ...

  8. gRPC:Google开源的基于HTTP/2和ProtoBuf的通用RPC框架

    gRPC:Google开源的基于HTTP/2和ProtoBuf的通用RPC框架 gRPC:Google开源的基于HTTP/2和ProtoBuf的通用RPC框架 Google Guava官方教程(中文版 ...

  9. 转战WebApp: 最适合Android开发者的WebApp框架

    随着移动端设备越来越多, 微信应用号即将发布, 越来越多的页面需要被移动浏览器承载, HTML5开发大热, 我们需要掌握Web开发的技能来适应时代变化. 合适的WebApp框架 AndroidUI4W ...

随机推荐

  1. Atitit 软件工程概览attilax总结

    Atitit 软件工程概览attilax总结 1.1. .2 软件工程的发展 进一步地,结合人类发展史和计算机世界演化史来考察软件工程的发展史. 表2 软件工程过程模型 表2将软件工程的主要过程模型做 ...

  2. 在开源中国(oschina)git中新建标签(tags)

    我今天提交代码到主干上面,本来想打个标签(tags)的. 因为我以前新建过标签(tags),但是我现在新建的时候不知道入库在哪了.怎么找也找不到了. 从网上找资料也没有,找客服没有人理我,看到一个交流 ...

  3. DBobjectsCompareScript(数据库对象比较).sql

    use master goIF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[func_CompareDBobjectsReColu ...

  4. 虚拟机VMware12.05下安装Ubuntu16.04几个关键地方

    在踩了自己按照网上的教程安装Ubuntu之后,仍然踩了不少坑,鼓捣了一段时间,才达到自己想要的界面.   下面就来说说,大家可能也会遇到的情况:   1.安装ISO镜像时候,路径直接选择 你从Ubun ...

  5. linux yum命令详解

    yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器.基於RPM包管理,能够从指定的服务器自动下载RP ...

  6. 再见Windows C++

    我3年多以前写过一个小工具,是用来检测Windows操作系统的版本及其所安装的.NET Framework版本的,我用它来排查由于缺乏运行环境支持所导致的程序无法运行的问题.这个工具是用Visual ...

  7. ABP框架 - 缓存

    文档目录 本节内容: 简介 ICacheManager ICache ITypedCache 配置 实体缓存 EntityCache 是如何工作 Redis 缓存集成 简介 ABP提供了一个缓存接口, ...

  8. java运行时获得泛型类型

    引言 众所周知,java泛型最重要的特征是泛型擦除,所有泛型在编译时会转换成Object所以在java中运行时无法获得泛型的类型. 但是其实以上的规则是针对方法的内部变量的,如果是其他形式的泛型其实是 ...

  9. Esay ui数据加载等待提示

    以视频上传为例: //视频上传    function uploadVedio(fileName){            load();//开始加载时弹出加载层        $.post('< ...

  10. ERP软件的价格设计

    ERP体现出信息流.资金流.物流在供应商.企业.客户间的运营方向,是以销售为源头,生产.物料需求计划为核心,以金额.实时数据为基础的整体.ERP的核心是MRP(物料需求).企业的经营活动最终是为了赢利 ...