响应式设计的意义

随着移动设备的发展,移动设备以迅猛的势头分刮着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. Set up VLAN (802.1q) tagging on a network interface?

    SOLUTION VERIFIED October 13 2015 KB39674 KB741413 environment Red Hat Enterprise Linux 4 Red Hat En ...

  2. raspberrypi(树莓派)上安装mono和jexus,运行asp.net程序

    参考网址: http://www.linuxdot.net/ http://www.cnblogs.com/mayswind/p/3279380.html http://www.raspberrypi ...

  3. mysql集群(双主)

    0.安装 所谓双主基本可以理解为两台服务器互为主备,其核心思路与主备配置相同. 服务器A: 内网IP: 10.44.94.219 服务器B: 内网IP: 10.44.94.97 1.配置服务器A lo ...

  4. [ 技术人员创业Tips ] 1:抓住优质客户(上)

    写一篇技术以外的内容,可能会得罪一些人,轻拍,此外本文写的比较随意,写到哪里算哪里,轻拍. IT业不知道从什么时候起特别流行谈创业,似乎不谈创业就落伍,我不评价这种风气的好坏,只提一些自己的一些经验和 ...

  5. ASP.NET Web API 控制器执行过程(一)

    ASP.NET Web API 控制器执行过程(一) 前言 前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在 ...

  6. js自动提示查询添加功能(不是自动补全)

    在工作中遇到查询某些数据,并添加到一个列表里的时候,写了一个小功能. 优点: 1.纯手工JS代码,不需要任何js框架,复制下来就能测试,无毒副作用. 2.通过模糊查询快速定位数据,并添加到列表里. 缺 ...

  7. idea报错:error java compilation failed internal java compiler error

    idea下面报如下问题 error java compilation failed internal java compiler error 解决办法:Setting->Compiler-> ...

  8. zk 起别名时候碰到的问题

    第一次搭建时候都是用的ip,没什么问题,看到别人都是用的别名,于是也想试试把ip改成别名.然而 其中碰到的问题 ,快一周了才解决,现在记录下: 1.改主机别名 一直以为 修改 /etc/hosts 里 ...

  9. vue中,class、内联style绑定、computed属性

    1.绑定Class ①对象语法 <li :class="{ 'active': activeIdx==0 }" @click="fnClickTab(0)" ...

  10. 4、DES和RSA简介

    DES是分组加密算法,速度快,使用单一密钥,加密解密都使用同一个密钥,一般用于大量数据加密,目前处于半淘汰状态. RSA算法是流式加密算法,速度慢,但是使用成对的密钥,加密解密使用不同的密钥,有利于保 ...