1、Mobile HTML,CSS,and JavasScript

url: http://www.slideshare.net/franksvalli/mobile-html-css-and-javascript
2、Mobile Web & HTML5 Performance Optimization

url: http://www.slideshare.net/firt/mobile-web-html5-performance-optimization
3、HTML5, CSS3, and other fancy buzzwords

url: http://www.slideshare.net/batmoo/html5-css3-and-other-fancy-buzzwords
背景:
momo 3g版,需要在手机WEB上播放录音(mp3格式),用了audio标签,不过发现android(2.2,2.3)对这个html5标签支持不好,iphone4完全正常。
判断方法是:
1、(‘Audio’ in window) && !! new Audio().canPlayType(‘audio/mpeg’) 确定用audio标签
核心代码:
//保证播放器单例
if(!MT.audio){
MT.audio = new Audio();
}
MT.audio.src = url;
MT.audio.play();
MT.audio.addEventListener("ended",function(){
//播放完成,更新UI
},false);
2、判断是否支持flash,用flash播放
判断flash:
if (navigator.plugins && navigator.plugins.length && navigator.plugins['Shockwave Flash']) {
return true;
} else if (navigator.mimeTypes && navigator.mimeTypes.length) {
var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
return mimeType && mimeType.enabledPlugin;
}
flash录音播放(as)核心代码:
import flash.external.*;
ExternalInterface.addCallback("playAudio",this,playAudio);
ExternalInterface.addCallback("stopAudio",this,stopAudio);
var mp3 = new Sound();
mp3.setVolume(300);
//播放结束
mp3.onSoundComplete=function(){
//触发外部JS方法
ExternalInterface.call("audioForFlash.trackEnded");
}
//播放音乐
function playAudio(url){
mp3.stop();
mp3.loadSound(url,true);
mp3.onLoad = function(){
mp3.start();
}
}
//停止播放
function stopAudio(){
mp3.stop();
}
setTimeout(function(){
//提示外部加载完毕
ExternalInterface.call("audioForFlash.loadStarted");
},100);
3、其它情况无法播放,提供链接下载
—————————————————-
android平台手机测试了几款,发现,有些能播放,有些不能播放(前提浏览器都装有flash插件)
经过一下午的折腾,总算解决问题了。
总结如下:
1、flash对象不能动态方式在后期append到dom,只能随页面一起加载
2、flash中调用到的外部js方法,必须出现在flash的对象之前(曾经在ie6中遇到类似情况)
3、flash对象只有在可见区域才能被激活,也就是说flash不能display:none,也不能在页面看不到的地方(如页面底部,需要滚动才能看到),不过可以用visibility:hidden;
前两个问题好办,第三个问题很诡异,现象总结如下:
3.1、页面很长,flash在底部,加载页面时候看不到,flash不会被激活
3.2、滚动页面,flash可见时候会继续被激活
3.3、继续滚动页面,flash不可见,flash中的动作中止(如录音播放一半中止)
解决方案:
动态设置flash对象的position,使其在播放时候可见。
核心代码:
//在点击播放按钮,或者滚动页面时执行以下代码
document.getElementById("flash_box").style.top = 60 + document.documentElement.scrollTop || 0 )+'px';
//用于“激活”页面
document.documentElement.scrollTop+=1;
注意后面那行代码,如果不通过这种方式手动激活页面,flash还是被认为不可见。
成果截图:
核心其实是一个plist文件和一个itms-services的链接协议,通过字段的正确配置,就可以直接在iphone网页上安装app了。
链接示例:
<a href=”itms-services://?action=download-manifest&url=http://3g.momo.im/down/momomomo.plist”>安装移动momo</a>
plist文件示例:
针对momo触屏版的需要,写了一个精简版js框架,专用于支持html5选择器的项目,如momo 手机web触屏版。
接口规则与jquery保持一致,用过jquery的看了下面的DEMO一定觉得很熟悉。目前仅分享基础框架,只有240行代码,包括:ajax操作(get,post,getJSON,ajax)、最基本的dom(show,hide,hasClass,addClass,removeClass,replaceWith,remove…)操作等。当然和jquery一样,您可以随便通过 extend方法去无限扩展框架,打造适合自己的JS组件或者框架。
目前版本:v0.02
最后更新时间:2011-11-22
大小:15K,压缩后5K,gzip后3K
框架DEMO:http://jslover.com/wp-content/uploads/2011/11/motouch/demo.htm
源码:http://jslover.com/wp-content/uploads/2011/11/motouch/moTouch.js

//demo示例
(function ($) {
//ajax get方法
$.get('json_test.js', { r: Math.random() }, function (data) { alert(data) });
// 支持任意扩展
$.extend({
alert: function (val) {
alert(val);
}
});
// 支持任意扩展
$.fn.extend({
newFunc: function () {
alert('newFunc');
return this;
}
});
$.alert('这是扩展的方法');
$('body').newFunc();
// 获取对象
var $div = $('div');
//对象个数
alert($div.length);
//添加样式
$div.addClass('class1');
//替换节点
$('img').replaceWith('不显示图片');
})(moTouch);
说到html5缓存,很自然就想到manifest,为了提高3g momo的体验,我们也特意尝试了这个,甚至做到了完全离线使用WEB页,像客户端一样,离线查询通讯录等。
不过,使用manifest的过程中,遇到了一个没法解决的问题:文件更新。理想状态,可以通过修改.manifest文件达到通知版本更新的效果,不过现实很不乐观,测试结果,在iphone的safari及pc chrome浏览器中,基本正常,修改.manifest文件后强制刷新两次页面就达到更新缓存文件的效果,至于为什么是2次,不想去纠结,至少它真能更新了。
在FF中,只能通过”工具-选项-脱机数据“删除缓存来达到更新效果,至于为什么更新.manifest起不到作用?我猜想.manifest文件也被缓存了吧,总之,FF上想用文件缓存又想随时通过代码更新,想都别想了。同样,在android机子(我用g6测试)上也是类似状况,甚至更恶劣,几乎快抓狂了。
查询了所有网上资源,几乎都是千篇一律,异想天开的理想主义,最全的一篇大概是这样:http://www.whatwg.org/specs/web-apps/current-work/multipage/offline.html。痛恨那些胡乱转载的门户,鄙视那些不经测试就贴代码的2B青年,特别指出,通过iframe页设置manifest的方式是不可取的,不知道那篇文章的作者是怎么测试的,总之目前版的浏览器是不支持这样缓存的。
——总之,为了兼容,我必须放弃manifest————————————–
于是,放弃“离线应用”,转向文件缓存,这样就容易多了,最终,决定将所有文件读取下来后通过localStorage对象以key-value形式存储,然后再通过eval执行脚本。
原理很简单,代码贴在后面,要注意的一点还是更新机制。
我设计的更新机制很简单:localStorage对象中存储最后更新的版本号,每次页面刷新时候与服务端的版本号变量进行比对,不等就更新,否则继续使用,当然在UI中也提供了手动更新的方法,确保随时对版本文件可控。
——-经常多平台测试,完全正常,随时可以更新———————————–
参考代码:这段代码挂在页面底部
//当前脚本版本号
var script_server_version = '0.3';
//待请求脚本队列
var script_list = ['/lib/lib.js', '/script/index.js', '/script/template.js'];
(function () {
//异步获取文件
function ajaxGet(url, callback) {
var request = new XMLHttpRequest();
if (!request) { return; }
request.open("GET", url, true);
request.onreadystatechange = function () {
if (request.readyState == 4) {
callback(request.responseText);
}
};
request.send(null);
}
//临时变量
var scriptCache = '';
var index = 0;
//循环加载
function getScript() {
ajaxGet(script_list[index] + '?v=' + script_server_version, function (data) {
scriptCache += data;
if (++index < script_list.length) {
getScript();
} else {
//存入缓存
localStorage['script_cache'] = scriptCache;
//存入当前版本号
localStorage['script_version'] = script_server_version || 0;
setTimeout(function () {
//执行脚本
eval(scriptCache);
}, 100);
}
});
}
//直接输出脚本
function writeScript() {
for (var i = 0; i < script_list.length; i++) {
document.write('..输出脚本,被过滤了,各位自己实现..');
}
}
//开始请求脚本
try {
//支持缓存
if ('localStorage' in window) {
//服务器保存的脚本版本,通过页面直接输出变量
var version_server = script_server_version || 0;
//本地版本号
var version_local = localStorage['script_version'] || -1;
//如果版本号不一致,或者不存在缓存
if (version_server != version_local || !localStorage['script_cache']) {
getScript();
} else {
//执行脚本
setTimeout(function () {
eval(localStorage['script_cache']);
}, 100);
}
} else {
writeScript();
}
} catch (ee) {
writeScript();
}
})();
html5的canvas对象和as3的绘图对象差不多,总结来说就是样式、moveTo、lineTo,当然,需要重绘功能,就要自己建立history堆栈。
功能很简单,html5 api很强大,简单几行就可以实现大多功能,在此基础上,可以通过拓展,设计线条样式、增加图形、文字等,甚至还能增加滤镜功能。
贴上demo,大家自己看吧,注意浏览器需要支持html5。 html5draw.htm
//绘图板
function Drawbox(canvas) {
this.canvas = canvas;
this.context = this.canvas.getContext('2d');
//线条宽度
this.context.lineWidth = 2;
//线条颜色
this.context.strokeStyle = '#555';
//线条圆角
this.context.lineCap = "round";
//状态
this.drawing = false;
//历史记录
this.his = [];
this.timeout_draw = 0;
}
Drawbox.prototype = {
mousedown: function (e) {
this.drawing = true;
this.his.push({
x: e.layerX
, y: e.layerY
});
}
, mouseup: function (e) {
this.drawing = false;
this.his.push({
x: e.layerX
, y: e.layerY
//是否中止,鼠标放开时,记录状态,防止“连笔”
, end: 1
});
}
, mousemove: function (e) {
var _this = this;
if (_this.drawing) {
//将坐标不断存入历史记录
_this.his.push({
x: e.layerX
, y: e.layerY
});
_this.context.beginPath();
var l = _this.his.length;
if (!_this.his[l - 1].end) {
_this.context.moveTo(_this.his[l - 2].x, _this.his[l - 2].y);
_this.context.lineTo(_this.his[l - 1].x, _this.his[l - 1].y);
_this.context.stroke();
}
}
}
, init: function () {
var _this = this;
_this.canvas.addEventListener('mousemove', function (e) { _this.mousemove(e) }, false);
_this.canvas.addEventListener('mousedown', function (e) { _this.mousedown(e) }, false);
_this.canvas.addEventListener('mouseup', function (e) { _this.mouseup(e) }, false);
}
//清空
, clear: function (noClearHis) {
var _this = this;
clearTimeout(_this.timeout_draw);
//清空
_this.context.clearRect(0, 0, 3000, 3000);
if (!noClearHis) {
//清除历史记录
_this.his.length = 0;
}
}
//重绘
, reDraw: function () {
var _this = this;
_this.clear(true);
var i = 0;
var length = _this.his.length;
clearTimeout(_this.timeout_draw);
if (length < 2) { return; }
function draw() {
if (!_this.his[i].end) {
_this.context.moveTo(_this.his[i].x, _this.his[i].y);
_this.context.lineTo(_this.his[i + 1].x, _this.his[i + 1].y);
_this.context.stroke();
}
_this.timeout_draw = setTimeout(function () {
if (i < length - 1) {
draw();
}
}, 10);
++i;
}
draw();
}
//导出PNG图片
, toPng: function () {
var url = this.canvas.toDataURL();
if (document.getElementById("temp_img")) {
document.getElementById("temp_img").src = url;
} else {
var img = document.createElement('img');
img.id = "temp_img";
img.src = url;
document.body.appendChild(img);
}
}
};
var draw = new Drawbox(document.getElementsByTagName('canvas')[0]);
draw.init();
核心代码:window.scrollTo(0, 1);
要注意的几个点:这个方法要在内容加载完成后执行,换句话浏览器内容的高度要超过浏览器窗口高度(出现‘滚动条’)才有效。
针对这个高度自适应的窗口(height:100%),得用特殊方式解决:
//强制让内容超过
$('#main').css("height",window.innerHeight+100);
window.scrollTo(0, 1);
//重置成新高度
$("#main").css("height",window.innerHeight);
//非常重要,用于兼容不同机型,防止浏览器窗口移动
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
另外,最后一行,用于阻止手动滚动页面
document.addEventListener(‘touchmove‘, function (e) { e.preventDefault(); }, false);
对,就是这么简单一句,却非常重要。
在做momo web touch版的时候,android系统主要都是针对htc系列手机,转QA测试后,在索爱x8系列的手机中,出现BUG,浏览器会随着内容移动,导致iscroll组件出现异常。
问题环境:
1、iphone的safari浏览器,有个特点,在屏保或者最小化浏览器后,会自己暂停所有浏览器进程。
2、通过js接入rabbitmq进行长轮询,原理是保持一个永不间断的http请求,因为上面的问题,导致在最小化浏览器时候这个http请求会被暂停,导致服务端超时,造成了长轮询被迫中断。
解决方案:
1、改用websocket或者其它方式连接mq(不在本次讨论范围);
2、轮询,监听mq状态。
想实现状态监听就必须找到相关的接口,rabbitmq的js组件,找不到官方维护文档,没有api文档,没有注释,只好debug打log。最终找到了两个重要接口:
打开连接后会返回通道对象,这个对象有一个.alive 的属性,用于检测状态。
另外,有一个公共方法 .close(),用于关闭连接。


