仅仅利用media query适配样式是远远不够的,并没有考虑触屏下的行为和特有的内容组织方式的不同。简单在桌面版基础上叠加mobile版的代码,会带来请求增多、流量、性能、代码冗余等诸多方面问题。有统计说86%的手机站看起来small其实体积比桌面版还大。
我们这次充分发挥“响应”的灵活性,实现one web。
响应式开发就为了实现one web:
1. 响应性的模块
原本网站的模块化程度越高越便于做响应性开发。一个page例如是这样组织的:
<%include file=”path/mod1.html” args=”data=data” />
<%include file=”path/mod2.html” args=”data=data” />
如果在手机下访问,模板系统在生成这个页面时,会在path/下找mobile.mod1.html,有则加之,否则加mod1.html。也就是说在同一目录下,会存在多个版本的模块,当前只有2种:mod1.html(桌面版),mobile.mod1.html(mobile版)
响应部分的代码,跟主站代码是放在一起的,这样更便于维护。一个页面模板的结构是这样的:
page1.html:
<%inherit file=”/base.html” />
<%def name=”main”>
桌面版主要内容
<%include file=”path/mod1.html” args=”data=data” />
</%def>
<%def name=”sidebar”>
桌面版边栏内容
</%def>
<%def name=”mobile_main”>
<%block filter=”collect_css”>
mobile版css
</%block>
mobile版主要内容。如果可以复用,直接调${self.main()}
很多情况下内容是不同的,比如去掉不必要的模块。
</%def>
<%def name=”mobile_sidebar”>
mobile版底部内容
</%def>
这意味可以同时开发/维护两个版本。(同样,设计师在设计一个页面,也需要秉承mobile first的原则)
在同一目录、同一文件维护比分布在不同的仓库中要方便的多。
2. 响应性的css/js
mobile版的变化很大,在样式上并不是桌面版css+mobile版css的关系。这得益于我们之前对静态文件管理系统的改造。传统的css的组织方式是集中式的,集中在几个通用文件中,形如base.css + product.css。而我们现在的方式是base.css + mod_1.css(inline) + mod_2.css(inline) + mod_3.css(inline) … 是按需组合的形式。
这样,加上设备判断后就可以轻易变成:
mobile.base.css + mobile.mod_1.css(inline) + mobile.mod_2.css(inline) + mobile.mod_3.css(inline) …
css/js文件跟模板一样,在同一目录下分别有桌面版和mobile版。根据访问端的情况,自动适配、按需组合。这样可以得到一个更优化的mobile站。
3. 增强触屏行为和兼容桌面事件
前者是指附加触屏上特有的事件:touchstart/touchmove/touchend以及手势swip/pinch/rotate/shake。这个不是难点。
mobile浏览器和桌面浏览器的事件模型有明显差异,为了完全复用桌面版的各种js组件,首要问题是设法兼容桌面事件(click和mouse事件)。
mobile上的click和mouse事件有几个需要注意的地方:
a. click和mouse事件不会发生在不可点击的元素上,意味绑在document上的事件代理完全失效
b. mouse事件是发生在手指离开屏幕后,且顺序是mouseover > mousemove > mousedown > mouseup
c. click事件最后触发。从手指离开屏幕起,有约300多毫秒延迟,而且有可能不会被触发
见下图:
“If the user taps a clickable element, events arrive in this order: mouseover, mousemove, mousedown, mouseup, and click. The mouseout event occurs only if the user taps on another clickable item. Also, if the contents of the page changes on the mousemove event, no subsequent events in the sequence are sent.”(出处)
android/ios不支持beforeunload事件,对unload事件的支持有些怪异,需要用pageshow/pagehide事件替代。
以上事件的差异都是要尽力消除的。解决思路是利用jQuery的special event机制覆盖掉原本的事件绑定。即:node.click(fn),mobile上转向node.touchend(fn) 。实现的代码:https://gist.github.com/3358036
4. 优化和用户体验
a.去掉了apple-mobile-web-app-capable声明。单页应用要加上,用响应式开发的加上这句体验反而不好,跳转的链接会弹出窗口打开。
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no"/>
## <meta name="apple-mobile-web-app-capable" content="yes">
b. “If the user taps a nonclickable element, no events are generated.”(引自Apple Dev Center),所以:
body {
cursor:pointer;
}
c. 设定内容中图片的最大宽:
.topic-content img {
max-width:100%;
}
d. 加载提示,感觉像异步加载。
e. application cache本身是为web app设计的,在响应式开发中的应用是不同的。
把大文件在手机端cache起来:
CACHE MANIFEST
# version 0.0.1
CACHE:
${static('/js/jquery.min.js')}
${static('/js/do.js')}
${static('/css/mobile/base.css')}
${image_url('/pics/icon/dou.png')}
${static('/css/ui/dialog.css')}
${static('/js/ui/dialog.js')}
${static('/js/mobile/jquery.mobile.events.js')}
这个文件是动态生成的,好处是当文件更新后,文件名中的签名会跟着变,也就会触发手机端app cache的更新。
判断cache更新:
if (window.applicationCache) {
window.applicationCache.addEventListener('updateready',function(){
window.applicationCache.swapCache();
}, false);
}
为了避免动态页面被cache,在一个隐藏的iframe里指定它。(更改,还需要进一步测试)
f. mobile上的UI库,比桌面版更有必要。
5. 调试和监测
用了两个工具adobe shadow和自带的tcpdump,对android/ios均适用。
a. adobe shadow 出了一段时间了。原来的问题是它要走adobe的weinre server,慢!终于最新版4已经可以指定本地的weinre server了。怎么启用本地weinre server是另外话题自己去搜吧,我是用jar启动的,作者网站提供各种版本:http://people.apache.org/~pmuellr/weinre/builds/1.x/
>java -jar ~/weinre-jar/weinre.jar –boundHost 10.0.2.48 (ifconfig查具体ip是什么)
weinre server启动参数:
–httpPort [portNumber] | 改变HTTP服务器的端口号 | 8080 |
–boundHost [hostname | ip address | -all-] | 改变主机名。如果使用默认的localhost,将无法从另一台机器访问该服务器 | localhost |
–verbose [true | false] | 记录标准输出行为 | false |
–debug [true | false] | 详细的操作日志输出到标准输出 | false |
–deathTimeout [seconds] | 指定超时 | 3 |
安装shadow,同时手机上安装shadow client。
指定本地server:
手机打开shadow client直接找ip。shadow其实就是对weinre包装了一层,它的inspector其实就是weinre调web inspector调试。
b. 用tcpdump监测http请求(参考这里)。步骤:
step 1: 建一个wifi热点
step 2: 用tcpdump命令捕获tcp的traffic,命令:
sudo tcpdump -i en1 -n -s 0 -w group.pcap tcp or port 53
(参考tcpdump用法http://www.tcpdump.org/tcpdump_man.html)
step 3: 手机联上热点,打开网站(先清cache)
step 4: Ctrl+c停掉tcpdump,log保存到指定的group.pcap文件中。pcap(packet capture)
step 5: pcap转成har文件浏览,http://pcapperf.appspot.com 或导入到charles里查看也很方便,分别看下图:
c. 在线工具:mobitest.akamai.com (说是开源了)